Merge "Add test for two new SmsManager methods" into sc-dev
diff --git a/apps/CameraITS2.0/config.yml b/apps/CameraITS2.0/config.yml
index bc19025..af8bc45 100644
--- a/apps/CameraITS2.0/config.yml
+++ b/apps/CameraITS2.0/config.yml
@@ -13,7 +13,7 @@
# limitations under the License.
TestBeds:
- - Name: TEST_BED_TABLET_SCENES
+ - Name: TEST_BED_TABLET_SCENES # Need 'tablet' in name for tablet scenes
# Test configuration for scenes[0:4, 6, _change]
Controllers:
AndroidDevice:
@@ -29,7 +29,7 @@
camera: <camera-id>
scene: <scene-name> # if <scene-name> left as-is runs all scenes
- - Name: TEST_BED_SENSOR_FUSION
+ - Name: TEST_BED_SENSOR_FUSION # Need 'sensor_fusion' in name for SF tests
# Test configuration for sensor_fusion/test_sensor_fusion.py
Controllers:
AndroidDevice:
diff --git a/apps/CameraITS2.0/tests/its_base_test.py b/apps/CameraITS2.0/tests/its_base_test.py
index 8c5ea76..1f96200 100644
--- a/apps/CameraITS2.0/tests/its_base_test.py
+++ b/apps/CameraITS2.0/tests/its_base_test.py
@@ -24,7 +24,8 @@
import its_session_utils
ADAPTIVE_BRIGHTNESS_OFF = '0'
-DISPLAY_TIMEOUT = 1800000 # ms
+TABLET_CMD_DELAY_SEC = 0.5 # found empirically
+TABLET_DIMMER_TIMEOUT_MS = 1800000 # this is max setting possible
CTS_VERIFIER_PKG = 'com.android.cts.verifier'
MBS_PKG_TXT = 'mbs'
MBS_PKG = 'com.google.android.mobly.snippet.bundled'
@@ -136,7 +137,11 @@
time.sleep(WAIT_TIME_SEC)
def setup_tablet(self):
- self.tablet.adb.shell(['input', 'keyevent ', 'KEYCODE_WAKEUP'])
+ # KEYCODE_POWER to reset dimmer timer. KEYCODE_WAKEUP no effect if ON.
+ self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_POWER'])
+ time.sleep(TABLET_CMD_DELAY_SEC)
+ self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_WAKEUP'])
+ time.sleep(TABLET_CMD_DELAY_SEC)
# Turn off the adaptive brightness on tablet.
self.tablet.adb.shell(
['settings', 'put', 'system', 'screen_brightness_mode',
@@ -148,7 +153,7 @@
logging.debug('Tablet brightness set to: %s',
format(self.tablet_screen_brightness))
self.tablet.adb.shell('settings put system screen_off_timeout {}'.format(
- DISPLAY_TIMEOUT))
+ TABLET_DIMMER_TIMEOUT_MS))
self.tablet.adb.shell('am force-stop com.google.android.apps.docs')
def parse_hidden_camera_id(self):
diff --git a/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf
index 92753c4..4bf85ee 100644
--- a/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf
+++ b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf
index 3103cd8..a9a2fbc 100644
--- a/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf
+++ b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py b/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py
index b85bec0..dc37d7b 100644
--- a/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py
+++ b/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py
@@ -131,33 +131,34 @@
pylab.ylabel('RGB patch means')
matplotlib.pyplot.savefig(
'%s_plot_means.png' % os.path.join(log_path, NAME))
-
# Ensure that image is not clamped to white/black.
- assert all(RGB_RANGE_THRESH < g_means[i] < 1.0-RGB_RANGE_THRESH
- for i in capture_idxs), 'Image too dark/bright! Check setup.'
+ if not all(RGB_RANGE_THRESH < g_means[i] < 1.0-RGB_RANGE_THRESH
+ for i in capture_idxs):
+ raise AssertionError('Image too dark/bright! Check setup.')
# Expect G0 == G1 == G2, R0 == 0.5*R1 == R2, B0 == B1 == 0.5*B2
# assert planes in caps expected to be equal
- e_msg = 'G[0] vs G[1] too different. [0]: %.3f, [1]: %.3f' % (
- g_means[0], g_means[1])
- assert abs(g_means[1] - g_means[0]) < RGB_DIFF_THRESH, e_msg
- e_msg = 'G[1] vs G[2] too different. [1]: %.3f, [2]: %.3f' % (
- g_means[1], g_means[2])
- assert abs(g_means[2] - g_means[1]) < RGB_DIFF_THRESH, e_msg
- e_msg = 'R[0] vs R[2] too different. [0]: %.3f, [2]: %.3f' % (
- r_means[0], r_means[2])
- assert abs(r_means[2] - r_means[0]) < RGB_DIFF_THRESH, e_msg
- e_msg = 'B[0] vs B[1] too different. [0]: %.3f, [1]: %.3f' % (
- b_means[0], b_means[1])
- assert abs(b_means[1] - b_means[0]) < RGB_DIFF_THRESH, e_msg
+ if abs(g_means[1] - g_means[0]) > RGB_DIFF_THRESH:
+ raise AssertionError('G[0] vs G[1] too different. '
+ f'[0]: {g_means[0]:.3f}, [1]: {g_means[1]:.3f}')
+ if abs(g_means[2] - g_means[1]) > RGB_DIFF_THRESH:
+ raise AssertionError('G[1] vs G[2] too different. '
+ f'[1]: {g_means[1]:.3f}, [2]: {g_means[2]:.3f}')
+ if abs(r_means[2] - r_means[0]) > RGB_DIFF_THRESH:
+ raise AssertionError('R[0] vs R[2] too different. '
+ f'[0]: {r_means[0]:.3f}, [2]: {r_means[2]:.3f}')
+ if abs(b_means[1] - b_means[0]) > RGB_DIFF_THRESH:
+ raise AssertionError('B[0] vs B[1] too different. '
+ f'[0]: {b_means[0]:.3f}, [1]: {b_means[1]:.3f}')
# assert boosted planes in caps
- e_msg = 'R[1] not boosted enough. [0]: %.3f, [1]: %.3f' % (
- r_means[0], r_means[1])
- assert abs(r_means[1] - 2*r_means[0]) < RGB_DIFF_THRESH, e_msg
- e_msg = 'B[2] not boosted enough. [0]: %.3f, [2]: %.3f' % (
- b_means[0], b_means[2])
- assert abs(b_means[2] - 2*b_means[0]) < RGB_DIFF_THRESH, e_msg
+ if abs(r_means[1] - 2*r_means[0]) > RGB_DIFF_THRESH:
+ raise AssertionError('R[1] not boosted enough or too much. '
+ f'[0]: {r_means[0]:.4f}, [1]: {r_means[1]:.4f}')
+ if abs(b_means[2] - 2*b_means[0]) > RGB_DIFF_THRESH:
+ raise AssertionError('B[2] not boosted enough or too much. '
+ f'[0]: {b_means[0]:.4f}, [2]: {b_means[2]:.4f}')
+
if __name__ == '__main__':
test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf
index 92753c4..4bf85ee 100644
--- a/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf
+++ b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf
index 3103cd8..a9a2fbc 100644
--- a/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf
+++ b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_a/test_effects.py b/apps/CameraITS2.0/tests/scene2_a/test_effects.py
index d5bfcab..59c01ef 100644
--- a/apps/CameraITS2.0/tests/scene2_a/test_effects.py
+++ b/apps/CameraITS2.0/tests/scene2_a/test_effects.py
@@ -61,16 +61,9 @@
props = cam.override_with_hidden_physical_camera_props(props)
mono_camera = camera_properties_utils.mono_camera(props)
- # Calculate camera_fov which will determine the image to load on tablet.
- camera_fov = cam.calc_camera_fov(props)
- file_name = cam.get_file_name_to_load(self.chart_distance, camera_fov,
- self.scene)
- logging.debug('Displaying %s on the tablet', file_name)
-
- # Display the scene on the tablet depending on camera_fov.
- self.tablet.adb.shell(
- 'am start -a android.intent.action.VIEW -d file:/sdcard/Download/%s'%
- file_name)
+ # Load chart for scene.
+ its_session_utils.load_scene(
+ cam, props, self.scene, self.tablet, self.chart_distance)
# Determine available effects and run test(s)
effects = props['android.control.availableEffects']
diff --git a/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py
index f47ca45..e67000f 100644
--- a/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py
@@ -89,7 +89,7 @@
return data_set
-class TestLensMovementReporting(its_base_test.ItsBaseTest):
+class LensMovementReportingTest(its_base_test.ItsBaseTest):
"""Test if focus distance is properly reported.
Do unit step of focus distance and check sharpness correlates.
diff --git a/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py
index 0e0eefd..e616ab2 100644
--- a/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py
@@ -38,10 +38,9 @@
def check_edge_modes(sharpness):
"""Check that the sharpness for the different edge modes is correct."""
logging.debug(' Verify HQ is sharper than OFF')
- e_msg = 'HQ: %.5f, OFF: %.5f, RTOL: %.2f' % (
- sharpness[EDGE_MODES['HQ']], sharpness[EDGE_MODES['OFF']],
- SHARPNESS_RTOL)
- assert sharpness[EDGE_MODES['HQ']] > sharpness[EDGE_MODES['OFF']], e_msg
+ if sharpness[EDGE_MODES['HQ']] < sharpness[EDGE_MODES['OFF']]:
+ raise AssertionError(f"HQ: {sharpness[EDGE_MODES['HQ']]:.5f}, "
+ f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}")
logging.debug('Verify ZSL is similar to OFF')
e_msg = 'ZSL: %.5f, OFF: %.5f, RTOL: %.2f' % (
diff --git a/apps/CameraITS2.0/tools/run_all_tests.py b/apps/CameraITS2.0/tools/run_all_tests.py
index f150220..2ff409d 100644
--- a/apps/CameraITS2.0/tools/run_all_tests.py
+++ b/apps/CameraITS2.0/tools/run_all_tests.py
@@ -20,20 +20,15 @@
import sys
import tempfile
import time
-
import yaml
-
YAML_FILE_DIR = os.environ['CAMERA_ITS_TOP']
CONFIG_FILE = os.path.join(YAML_FILE_DIR, 'config.yml')
-TEST_BED_TABLET_SCENES = 'TEST_BED_TABLET_SCENES'
-TEST_BED_SENSOR_FUSION = 'TEST_BED_SENSOR_FUSION'
-PROC_TIMEOUT_CODE = -101 # terminated process return -process_id
-PROC_TIMEOUT_TIME = 900 # timeout in seconds for a process (15 minutes)
+TEST_KEY_TABLET = 'tablet'
+TEST_KEY_SENSOR_FUSION = 'sensor_fusion'
LOAD_SCENE_DELAY = 1 # seconds
ACTIVITY_START_WAIT = 1.5 # seconds
-
RESULT_PASS = 'PASS'
RESULT_FAIL = 'FAIL'
RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
@@ -48,6 +43,7 @@
EXTRA_RESULTS = 'camera.its.extra.RESULTS'
TIME_KEY_START = 'start'
TIME_KEY_END = 'end'
+
# All possible scenes
# Notes on scene names:
# scene*_1/2/... are same scene split to load balance run times for scenes
@@ -90,7 +86,7 @@
'scene_change': []
}
-_DST_SCENE_DIR = '/sdcard/Download/'
+_DST_SCENE_DIR = '/mnt/sdcard/Download/'
MOBLY_TEST_SUMMARY_TXT_FILE = 'test_mobly_summary.txt'
@@ -283,10 +279,10 @@
config_file_contents = get_config_file_contents()
for i in config_file_contents['TestBeds']:
if scenes == ['sensor_fusion']:
- if i['Name'] != TEST_BED_SENSOR_FUSION:
+ if TEST_KEY_SENSOR_FUSION not in i['Name'].lower():
config_file_contents['TestBeds'].remove(i)
else:
- if i['Name'] != TEST_BED_TABLET_SCENES:
+ if TEST_KEY_SENSOR_FUSION in i['Name'].lower():
config_file_contents['TestBeds'].remove(i)
# Get test parameters from config file
@@ -298,7 +294,7 @@
device_id = get_device_serial_number('dut', config_file_contents)
- if config_file_contents['TestBeds'][0]['Name'] == TEST_BED_TABLET_SCENES:
+ if TEST_KEY_TABLET in config_file_contents['TestBeds'][0]['Name'].lower():
tablet_id = get_device_serial_number('tablet', config_file_contents)
else:
tablet_id = None
diff --git a/apps/CameraITS2.0/utils/cv2_image_processing_utils.py b/apps/CameraITS2.0/utils/cv2_image_processing_utils.py
index dde4570..55f341d 100644
--- a/apps/CameraITS2.0/utils/cv2_image_processing_utils.py
+++ b/apps/CameraITS2.0/utils/cv2_image_processing_utils.py
@@ -391,15 +391,13 @@
if num_circles == 0:
image_processing_utils.write_image(img/255, img_name, True)
- logging.error('No black circle detected. '
- 'Please take pictures according to instruction carefully!')
- assert num_circles == 1
+ raise AssertionError('No black circle detected. '
+ 'Please take pictures according to instructions.')
if num_circles > 1:
image_processing_utils.write_image(img/255, img_name, True)
- logging.debug('More than 1 black circle detected. '
- 'Background of scene may be too complex.')
- assert num_circles == 1
+ raise AssertionError('More than 1 black circle detected. '
+ 'Background of scene may be too complex.')
return circle
diff --git a/apps/CameraITS2.0/utils/its_session_utils.py b/apps/CameraITS2.0/utils/its_session_utils.py
index 4794911..224b5ca 100644
--- a/apps/CameraITS2.0/utils/its_session_utils.py
+++ b/apps/CameraITS2.0/utils/its_session_utils.py
@@ -1120,8 +1120,8 @@
logging.debug('Displaying %s on the tablet', file_name)
# Display the scene on the tablet depending on camera_fov
tablet.adb.shell(
- 'am start -a android.intent.action.VIEW -d file:/sdcard/Download/%s'%
- file_name)
+ 'am start -a android.intent.action.VIEW -t application/pdf '
+ f'-d file://mnt/sdcard/Download/{file_name}')
time.sleep(LOAD_SCENE_DELAY_SEC)
rfov_camera_in_rfov_box = (
numpy.isclose(
diff --git a/apps/CtsVerifier/Android.bp b/apps/CtsVerifier/Android.bp
index 090cc40..460497b 100644
--- a/apps/CtsVerifier/Android.bp
+++ b/apps/CtsVerifier/Android.bp
@@ -6,6 +6,7 @@
android_test {
name: "CtsVerifier",
defaults: ["cts_error_prone_rules_tests"],
+ additional_manifests: ["AndroidManifest-common.xml"],
compile_multilib: "both",
diff --git a/apps/CtsVerifier/AndroidManifest-common.xml b/apps/CtsVerifier/AndroidManifest-common.xml
new file mode 100644
index 0000000..925f8aa
--- /dev/null
+++ b/apps/CtsVerifier/AndroidManifest-common.xml
@@ -0,0 +1,47 @@
+<?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.verifier">
+
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30"/>
+
+ <application android:networkSecurityConfig="@xml/network_security_config"
+ android:label="@string/app_name"
+ android:icon="@drawable/icon"
+ android:debuggable="true"
+ android:largeHeap="true"
+ android:requestLegacyExternalStorage="true"
+ android:allowBackup="false"
+ android:theme="@android:style/Theme.DeviceDefault">
+
+ <meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
+ android:value="true"/>
+
+ <activity android:name=".TestListActivity" android:label="@string/app_name" />
+
+ <activity android:name=".ReportViewerActivity"
+ android:configChanges="keyboardHidden|orientation|screenSize"
+ android:label="@string/report_viewer" />
+ </application>
+
+ <queries>
+ <!-- Rotation Vector CV Crosscheck (RVCVXCheckTestActivity) relies on OpenCV Manager -->
+ <package android:name="org.opencv.engine" />
+ </queries>
+</manifest>
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 1627837..67be612 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -16,9 +16,9 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.verifier"
- android:versionCode="5"
- android:versionName="11_r1">
+ package="com.android.cts.verifier"
+ android:versionCode="5"
+ android:versionName="11_r1">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30"/>
@@ -89,26 +89,10 @@
removed once tests are refactored to use the proper IPC between theses users. -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
- <application android:networkSecurityConfig="@xml/network_security_config"
- android:label="@string/app_name"
- android:icon="@drawable/icon"
- android:debuggable="true"
- android:largeHeap="true"
- android:requestLegacyExternalStorage="true"
- android:allowBackup="false"
- android:theme="@android:style/Theme.DeviceDefault">
+ <application>
<meta-data android:name="SuiteName" android:value="CTS_VERIFIER" />
- <meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
- android:value="true"/>
-
- <activity android:name=".TestListActivity" android:label="@string/app_name" />
-
- <activity android:name=".ReportViewerActivity"
- android:configChanges="keyboardHidden|orientation|screenSize"
- android:label="@string/report_viewer" />
-
<provider android:name=".TestResultsProvider"
android:authorities="com.android.cts.verifier.testresultsprovider"
android:grantUriPermissions="true"
@@ -3172,8 +3156,9 @@
</intent-filter>
<meta-data android:name="test_category" android:value="@string/test_category_security" />
<!-- KeyChain is only installed on communication-oriented devices inheriting core.mk -->
+ <!-- KeyChain is disabled for automotive as feature is not fully supported. -->
<meta-data android:name="test_excluded_features"
- android:value="android.hardware.type.watch:android.hardware.type.television:android.software.leanback" />
+ android:value="android.hardware.type.watch:android.hardware.type.television:android.software.leanback:android.hardware.type.automotive" />
<meta-data android:name="display_mode"
android:value="single_display_mode" />
</activity>
@@ -5439,9 +5424,4 @@
android:value="multi_display_mode" />
</activity>
</application>
-
- <queries>
- <!-- Rotation Vector CV Crosscheck (RVCVXCheckTestActivity) relies on OpenCV Manager -->
- <package android:name="org.opencv.engine" />
- </queries>
</manifest>
diff --git a/common/device-side/bedstead/activitycontext/Android.bp b/common/device-side/bedstead/activitycontext/Android.bp
index 285b5eb..c3f7540 100644
--- a/common/device-side/bedstead/activitycontext/Android.bp
+++ b/common/device-side/bedstead/activitycontext/Android.bp
@@ -1,6 +1,6 @@
android_library {
name: "ActivityContext",
- sdk_version: "26",
+ sdk_version: "27",
srcs: [
"src/main/java/**/*.java"
],
diff --git a/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml b/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
index 37f7c0c..dd320d9 100644
--- a/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
+++ b/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.activitycontext">
- <uses-sdk android:minSdkVersion="26" />
+ <uses-sdk android:minSdkVersion="27" />
<application>
<activity android:name=".ActivityContext" android:exported="true"/>
</application>
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
index 6c04ed7..ac907be 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
@@ -29,6 +29,7 @@
import android.annotation.Nullable;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -118,6 +119,8 @@
TestAppCallbacksReceiver.sendBroadcast(context, intent);
return;
}
+ Log.d(TAG, "Broadcasting " + intent.getAction() + " locally on user "
+ + context.getUserId());
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
@@ -128,10 +131,14 @@
// Methods that use CharSequence instead of String
if (methodName.equals("wipeData") && parameterTypes.length == 2) {
- // wipeData() takes a CharSequence, but it's wrapped as String
return clazz.getDeclaredMethod(methodName,
new Class<?>[] { int.class, CharSequence.class });
}
+ if ((methodName.equals("setStartUserSessionMessage")
+ || methodName.equals("setEndUserSessionMessage"))) {
+ return clazz.getDeclaredMethod(methodName,
+ new Class<?>[] { ComponentName.class, CharSequence.class });
+ }
// Calls with null parameters (and hence the type cannot be inferred)
Method method = findMethodWithNullParameterCall(clazz, methodName, parameterTypes);
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
index 646b853..c5f0e1f 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
@@ -142,6 +142,15 @@
doAnswer(answer).when(spy).wipeData(anyInt(), any());
doAnswer(answer).when(spy).wipeData(anyInt());
+ // Used by ListForegroundAffiliatedUsersTest
+ doAnswer(answer).when(spy).listForegroundAffiliatedUsers();
+
+ // Used by UserSessionTest
+ doAnswer(answer).when(spy).getStartUserSessionMessage(any());
+ doAnswer(answer).when(spy).setStartUserSessionMessage(any(), any());
+ doAnswer(answer).when(spy).getEndUserSessionMessage(any());
+ doAnswer(answer).when(spy).setEndUserSessionMessage(any(), any());
+
// TODO(b/176993670): add more methods below as tests are converted
} catch (Exception e) {
// Should never happen, but needs to be catch as some methods declare checked exceptions
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java
index 3df6d62..33f8d78 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java
@@ -20,6 +20,7 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
+import android.util.Log;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -28,6 +29,8 @@
*/
public final class TestAppHelper {
+ private static final String TAG = TestAppHelper.class.getSimpleName();
+
/**
* Called by test case to register a {@link BrodcastReceiver} to receive intents sent by the
* device owner's {@link android.app.admin.DeviceAdminReceiver}.
@@ -38,6 +41,8 @@
TestAppCallbacksReceiver.registerReceiver(context, receiver, filter);
return;
}
+ Log.d(TAG, "Registering " + receiver + " to receive " + Utils.toString(filter)
+ + " locally on user " + context.getUserId());
LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter);
}
@@ -50,6 +55,7 @@
TestAppCallbacksReceiver.unregisterReceiver(context, receiver);
return;
}
+ Log.d(TAG, "Unegistering " + receiver + " locally on user " + context.getUserId());
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
}
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
index 8a8ae70..65d4806 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
@@ -16,6 +16,7 @@
package com.android.bedstead.dpmwrapper;
import android.app.ActivityManager;
+import android.content.IntentFilter;
import android.os.UserHandle;
import android.os.UserManager;
@@ -49,6 +50,13 @@
+ "called by process from user " + MY_USER_ID);
}
+ static String toString(IntentFilter filter) {
+ StringBuilder builder = new StringBuilder("[");
+ filter.actionsIterator().forEachRemaining((s) -> builder.append(s).append(","));
+ builder.deleteCharAt(builder.length() - 1);
+ return builder.append(']').toString();
+ }
+
private Utils() {
throw new UnsupportedOperationException("contains only static methods");
}
diff --git a/common/device-side/bedstead/eventlib/Android.bp b/common/device-side/bedstead/eventlib/Android.bp
index 2010d72..e6f5d6c 100644
--- a/common/device-side/bedstead/eventlib/Android.bp
+++ b/common/device-side/bedstead/eventlib/Android.bp
@@ -9,7 +9,7 @@
"Nene",
"androidx.test.ext.junit"],
manifest: "src/main/AndroidManifest.xml",
- min_sdk_version: "26"
+ min_sdk_version: "27"
}
android_test {
@@ -32,5 +32,5 @@
],
data: [":EventLibTestApp"],
manifest: "src/test/AndroidManifest.xml",
- min_sdk_version: "26"
+ min_sdk_version: "27"
}
\ No newline at end of file
diff --git a/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml b/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml
index 19ce3fa..edbb897 100644
--- a/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml
+++ b/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.eventlib">
- <uses-sdk android:minSdkVersion="26" />
+ <uses-sdk android:minSdkVersion="27" />
<application>
<service android:name="com.android.eventlib.QueryService" android:exported="true"/>
</application>
diff --git a/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml b/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml
index 710df7b..061629e 100644
--- a/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml
+++ b/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.eventlib.test">
- <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
<application
android:label="Event Library Tests" android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
<uses-library android:name="android.test.runner" />
diff --git a/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp b/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp
index edf5790..8312d3b 100644
--- a/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp
+++ b/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp
@@ -8,5 +8,5 @@
"EventLib"
],
manifest: "src/main/AndroidManifest.xml",
- min_sdk_version: "26"
+ min_sdk_version: "27"
}
\ No newline at end of file
diff --git a/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml b/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml
index f915144..86cf73b 100644
--- a/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml
+++ b/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.eventlib.tests.testapp">
- <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
<application>
<activity android:name=".EventLoggingActivity"
android:exported="true">
diff --git a/common/device-side/bedstead/harrier/Android.bp b/common/device-side/bedstead/harrier/Android.bp
index 308a933..c94806f1 100644
--- a/common/device-side/bedstead/harrier/Android.bp
+++ b/common/device-side/bedstead/harrier/Android.bp
@@ -21,5 +21,7 @@
],
static_libs: [
+ "Nene",
+ "compatibility-device-util-axt"
]
}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
similarity index 73%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
index 8b83757..17c3471 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
@@ -14,40 +14,39 @@
* limitations under the License.
*/
-package com.android.compatibility.common.util.enterprise;
-
-import static android.app.UiAutomation.FLAG_DONT_USE_ACCESSIBILITY;
+package com.android.bedstead.harrier;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.core.app.ApplicationProvider;
-
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.harrier.annotations.EnsureHasTvProfile;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
import com.android.bedstead.harrier.annotations.FailureMode;
import com.android.bedstead.harrier.annotations.RequireFeatures;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnTvProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.ShellCommand;
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasSecondaryUser;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasTvProfile;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasWorkProfile;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnTvProfile;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -56,9 +55,8 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Scanner;
import java.util.Set;
+import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -77,6 +75,7 @@
public final class DeviceState implements TestRule {
private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final TestApis mTestApis = new TestApis();
private static final String SKIP_TEST_TEARDOWN_KEY = "skip-test-teardown";
private static final String SKIP_TESTS_REASON_KEY = "skip-tests-reason";
private final boolean mSkipTestTeardown;
@@ -90,7 +89,8 @@
public DeviceState() {
Bundle arguments = InstrumentationRegistry.getArguments();
- mSkipTestTeardown = Boolean.parseBoolean(arguments.getString(SKIP_TEST_TEARDOWN_KEY, "false"));
+ mSkipTestTeardown = Boolean.parseBoolean(
+ arguments.getString(SKIP_TEST_TEARDOWN_KEY, "false"));
mSkipTestsReason = arguments.getString(SKIP_TESTS_REASON_KEY, "");
mSkipTests = !mSkipTestsReason.isEmpty();
}
@@ -160,17 +160,20 @@
}
}
- Log.d("DeviceState", "Finished preparing state for test " + description.getMethodName());
+ Log.d("DeviceState",
+ "Finished preparing state for test " + description.getMethodName());
try {
base.evaluate();
} finally {
- Log.d("DeviceState", "Tearing down state for test " + description.getMethodName());
+ Log.d("DeviceState",
+ "Tearing down state for test " + description.getMethodName());
teardownNonShareableState();
if (!mSkipTestTeardown) {
teardownShareableState();
}
- Log.d("DeviceState", "Finished tearing down state for test " + description.getMethodName());
+ Log.d("DeviceState",
+ "Finished tearing down state for test " + description.getMethodName());
}
}};
}
@@ -181,17 +184,19 @@
private void requireFeature(String feature, FailureMode failureMode) {
if (failureMode.equals(FailureMode.FAIL)) {
- assertThat(mContext.getPackageManager().hasSystemFeature(feature)).isTrue();
+ assertThat(mTestApis.packages().features().contains(feature)).isTrue();
} else if (failureMode.equals(FailureMode.SKIP)) {
assumeTrue("Device must have feature " + feature,
- mContext.getPackageManager().hasSystemFeature(feature));
+ mTestApis.packages().features().contains(feature));
} else {
throw new IllegalStateException("Unknown failure mode: " + failureMode);
}
}
private void requireUserSupported(String userType) {
- assumeTrue("Device must support user type " + userType + " only supports: " + getSupportedProfileTypes(), getSupportedProfileTypes().contains(userType));
+ assumeTrue("Device must support user type " + userType
+ + " only supports: " + mTestApis.users().supportedTypes(),
+ mTestApis.users().supportedType(userType) != null);
}
public enum UserType {
@@ -387,7 +392,10 @@
final boolean removing;
final Set<String> flags;
- UserInfo(int id, String type, int parent, boolean isPrimary, boolean removing, Set<String> flags) {
+ UserInfo(
+ int id, String type,
+ int parent,
+ boolean isPrimary, boolean removing, Set<String> flags) {
this.id = id;
this.type = type;
this.parent = parent;
@@ -398,7 +406,15 @@
@Override
public String toString() {
- return "UserInfo{id=" + id + ", type=" + type + ", parent=" + parent + ", isPrimary=" + isPrimary + ", flags=" + flags.toString();
+ return "UserInfo{id=" + id
+ + ", type="
+ + type
+ + ", parent="
+ + parent
+ + ", isPrimary="
+ + isPrimary
+ + ", flags="
+ + flags.toString();
}
}
@@ -414,8 +430,12 @@
private Set<UserInfo> listUsers(boolean includeRemoving) {
- String command = "dumpsys user";
- String commandOutput = runCommandWithOutput(command);
+ String commandOutput = "";
+ try {
+ commandOutput = ShellCommand.builder("dumpsys user").execute();
+ } catch (AdbException e) {
+ throw new IllegalStateException("Error getting user list", e);
+ }
String userArea = commandOutput.split("Users:.*\n")[1].split("\n\n")[0];
Set<String> userStrings = new HashSet<>();
@@ -484,13 +504,13 @@
int workProfileId = getWorkProfileId(forUser);
// TODO(scottjonathan): Can make this quicker by checking if we're already running
- runCommandWithOutput("am start-user -w " + workProfileId);
+ mTestApis.users().find(workProfileId).start();
if (installTestApp) {
- installInProfile(workProfileId,
- sInstrumentation.getContext().getPackageName());
+ mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+ .install(mTestApis.users().find(workProfileId));
} else {
- uninstallFromProfile(workProfileId,
- sInstrumentation.getContext().getPackageName());
+ mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+ .uninstall(mTestApis.users().find(workProfileId));
}
}
@@ -501,11 +521,11 @@
createTvProfile(resolveUserTypeToUserId(forUser));
}
if (installTestApp) {
- installInProfile(getTvProfileId(forUser),
- sInstrumentation.getContext().getPackageName());
+ mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+ .install(mTestApis.users().find(getTvProfileId(forUser)));
} else {
- uninstallFromProfile(getTvProfileId(forUser),
- sInstrumentation.getContext().getPackageName());
+ mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+ .uninstall(mTestApis.users().find(getTvProfileId(forUser)));
}
}
@@ -515,10 +535,11 @@
createSecondaryUser();
}
if (installTestApp) {
- installInProfile(getSecondaryUserId(), sInstrumentation.getContext().getPackageName());
+ mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+ .install(mTestApis.users().find(getSecondaryUserId()));
} else {
- uninstallFromProfile(getSecondaryUserId(),
- sInstrumentation.getContext().getPackageName());
+ mTestApis.packages().find(sInstrumentation.getContext().getPackageName())
+ .uninstall(mTestApis.users().find(getSecondaryUserId()));
}
}
@@ -535,8 +556,17 @@
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiver(String action) {
+ return registerBroadcastReceiver(action, /* checker= */ null);
+ }
+
+ /**
+ * Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
+ * test has run.
+ */
+ public BlockingBroadcastReceiver registerBroadcastReceiver(
+ String action, Function<Intent, Boolean> checker) {
BlockingBroadcastReceiver broadcastReceiver =
- new BlockingBroadcastReceiver(mContext, action);
+ new BlockingBroadcastReceiver(mContext, action, checker);
broadcastReceiver.register();
registeredBroadcastReceivers.add(broadcastReceiver);
@@ -569,7 +599,7 @@
private void teardownShareableState() {
for (Integer userId : createdUserIds) {
- runCommandWithOutput("pm remove-user " + userId);
+ mTestApis.users().find(userId).remove();
}
createdUserIds.clear();
@@ -577,111 +607,51 @@
private void createWorkProfile(int parentUserId) {
requireCanSupportAdditionalUser();
- final String createUserOutput =
- runCommandWithOutput(
- "pm create-user --profileOf " + parentUserId + " --managed work");
- final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
- createdUserIds.add(profileId);
+ try {
+ int profileId = ShellCommand.builder("pm create-user")
+ .addOption("--profileOf", parentUserId)
+ .addOperand("--managed")
+ .addOperand("work")
+ .executeAndParseOutput(
+ output -> Integer.parseInt(output.split(" id ")[1].trim()));
+ mTestApis.users().find(profileId).start();
+ createdUserIds.add(profileId);
+ } catch (AdbException e) {
+ throw new IllegalStateException("Error creating work profile", e);
+ }
}
private void createTvProfile(int parentUserId) {
requireCanSupportAdditionalUser();
- final String createUserOutput =
- runCommandWithOutput(
- "pm create-user --profileOf " + parentUserId + " --user-type "
- + TV_PROFILE_TYPE + " tv");
- final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
- runCommandWithOutput("am start-user -w " + profileId);
- createdUserIds.add(profileId);
+ try {
+ int profileId = ShellCommand.builder("pm create-user")
+ .addOption("--profileOf", parentUserId)
+ .addOption("--user-type", TV_PROFILE_TYPE)
+ .addOperand("--managed")
+ .addOperand("tv")
+ .executeAndParseOutput(
+ output -> Integer.parseInt(output.split(" id ")[1].trim()));
+ mTestApis.users().find(profileId).start();
+ createdUserIds.add(profileId);
+ } catch (AdbException e) {
+ throw new IllegalStateException("Error creating work profile", e);
+ }
}
private void createSecondaryUser() {
requireCanSupportAdditionalUser();
- final String createUserOutput =
- runCommandWithOutput("pm create-user secondary");
- final int userId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
- runCommandWithOutput("am start-user -w " + userId);
- createdUserIds.add(userId);
- }
-
- private void installInProfile(int profileId, String packageName) {
- runCommandWithOutput("pm install-existing --user " + profileId + " " + packageName);
- }
-
- private void uninstallFromProfile(int profileId, String packageName) {
- runCommandWithOutput("pm uninstall --user " + profileId + " " + packageName);
- }
-
- private String runCommandWithOutput(String command) {
- ParcelFileDescriptor p = runCommand(command);
-
- try (Scanner scanner = new Scanner(new ParcelFileDescriptor.AutoCloseInputStream(p),
- UTF_8.name())) {
- String s = scanner.useDelimiter("\\A").next();
- Log.d("DeviceState", "Running command " + command + " got output: " + s);
- return s;
- } catch (NoSuchElementException e) {
- Log.d("DeviceState", "Running command " + command + " with no output", e);
- return "";
- }
- }
-
- private ParcelFileDescriptor runCommand(String command) {
- Log.d("DeviceState", "Running command " + command);
- return getAutomation()
- .executeShellCommand(command);
- }
-
- private UiAutomation getAutomation() {
- if (mUiAutomation != null) {
- return mUiAutomation;
- }
-
- int retries = MAX_UI_AUTOMATION_RETRIES;
- mUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_USE_ACCESSIBILITY);
- while (mUiAutomation == null && retries > 0) {
- Log.e(LOG_TAG, "Failed to get UiAutomation");
- retries--;
- mUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_USE_ACCESSIBILITY);
- }
-
- if (mUiAutomation == null) {
- throw new AssertionError("Could not get UiAutomation");
- }
-
- return mUiAutomation;
+ UserReference user = mTestApis.users().createUser().createAndStart();
+ createdUserIds.add(user.id());
}
private int getMaxNumberOfUsersSupported() {
- String command = "pm get-max-users";
- String commandOutput = runCommandWithOutput(command);
try {
- return Integer.parseInt(commandOutput.substring(commandOutput.lastIndexOf(" ")).trim());
- } catch (NumberFormatException e) {
+ return ShellCommand.builder("pm get-max-users")
+ .validate((output) -> output.startsWith("Maximum supported users:"))
+ .executeAndParseOutput(
+ (output) -> Integer.parseInt(output.split(": ", 2)[1].trim()));
+ } catch (AdbException e) {
throw new IllegalStateException("Invalid command output", e);
}
}
-
- private Set<String> mSupportedProfileTypesCache = null;
- private static final Pattern USER_TYPE_PATTERN = Pattern.compile("mName: (.+)");
-
- private Set<String> getSupportedProfileTypes() {
- if (mSupportedProfileTypesCache != null) {
- return mSupportedProfileTypesCache;
- }
-
- String command = "dumpsys user";
- String commandOutput = runCommandWithOutput(command);
-
- String userArea = commandOutput.split("User types \\(\\d+ types\\):")[1].split("\n\n")[0];
- mSupportedProfileTypesCache = new HashSet<>();
-
- Matcher matcher = USER_TYPE_PATTERN.matcher(userArea);
-
- while(matcher.find()) {
- mSupportedProfileTypesCache.add(matcher.group(1));
- }
-
- return mSupportedProfileTypesCache;
- }
}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
similarity index 88%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
index a9718bb..9419a48 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasTvProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
similarity index 82%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasTvProfile.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
index f085a9b..0345491 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasTvProfile.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
-import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
similarity index 83%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
index 55109d6..b2c1e25 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
-import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/Postsubmit.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
similarity index 91%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/Postsubmit.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
index a2eb6f6..bb9d248 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/Postsubmit.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
similarity index 85%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
index 808fbca..c30452e 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
similarity index 87%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
index 37894e0..6205df9 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnTvProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
similarity index 85%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnTvProfile.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
index 5cdda67..7a4f4cf 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnTvProfile.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
similarity index 87%
rename from common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java
rename to common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
index 4c3c816..70ddc97 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.compatibility.common.util.enterprise.annotations;
+package com.android.bedstead.harrier.annotations;
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/common/device-side/bedstead/nene/Android.bp b/common/device-side/bedstead/nene/Android.bp
index d21fab6..4719478 100644
--- a/common/device-side/bedstead/nene/Android.bp
+++ b/common/device-side/bedstead/nene/Android.bp
@@ -8,7 +8,7 @@
static_libs: [
"compatibility-device-util-axt",
],
- min_sdk_version: "26"
+ min_sdk_version: "27"
}
android_test {
@@ -30,7 +30,7 @@
],
data: [":NeneTestApp1"],
manifest: "src/test/AndroidManifest.xml",
- min_sdk_version: "26"
+ min_sdk_version: "27"
}
android_test_helper_app {
@@ -39,5 +39,5 @@
"EventLib"
],
manifest: "testapps/TestApp1.xml",
- min_sdk_version: "26"
+ min_sdk_version: "27"
}
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/src/main/AndroidManifest.xml b/common/device-side/bedstead/nene/src/main/AndroidManifest.xml
index a8d91e7..e269c76 100644
--- a/common/device-side/bedstead/nene/src/main/AndroidManifest.xml
+++ b/common/device-side/bedstead/nene/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.bedstead.nene">
- <uses-sdk android:minSdkVersion="26" />
+ <uses-sdk android:minSdkVersion="27" />
<application>
</application>
</manifest>
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
index b71106b..77b5dbb 100644
--- 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
@@ -16,21 +16,33 @@
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 command;
- private final String output;
+ 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.command = command;
- this.output = output;
+ 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) {
@@ -38,20 +50,32 @@
if (command == null) {
throw new NullPointerException();
}
- this.command = command;
- this.output = output;
+ this.mCommand = command;
+ this.mOutput = output;
+ this.mErr = null;
}
public String command() {
- return command;
+ return mCommand;
}
public String output() {
- return output;
+ return mOutput;
}
@Override
public String toString() {
- return super.toString() + "[command=\"" + command + "\" output=\"" + output + "\"]";
+ 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/KeepUninstalledPackagesBuilder.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java
new file mode 100644
index 0000000..b81764d
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.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 com.android.bedstead.nene.packages;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+
+/**
+ * Builder for a list of packages which will not be cleaned up by the system even if they are not
+ * installed on any user.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public final class KeepUninstalledPackagesBuilder {
+
+ private final List<String> mPackages = new ArrayList<>();
+
+ KeepUninstalledPackagesBuilder() {
+ }
+
+ /**
+ * Commit the collection of packages which will not be cleaned up.
+ *
+ * <p>Packages previously in the list but not in the updated list may be removed at any time if
+ * they are not installed on any user.
+ */
+ public void commit() {
+ // TODO(scottjonathan): Investigate if we can make this backwards compatible by pulling the
+ // APK files and keeping them (either as a file or in memory) until needed to resolve or
+ // re-install
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ PackageManager packageManager = context.getPackageManager();
+
+ // TODO(scottjonathan): Once we support permissions in Nene, use that call here
+
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ // Needs KEEP_UNINSTALLED_PACKAGES
+ packageManager.setKeepUninstalledPackages(mPackages);
+ });
+ }
+
+ /**
+ * Clear the list of packages which will not be cleaned up.
+ *
+ * <p>Packages previously in the list may be removed at any time if they are not installed on
+ * any user.
+ */
+ public void clear() {
+ mPackages.clear();
+ commit();
+ }
+
+ /**
+ * Add a package to the list of those which will not be cleaned up.
+ */
+ public KeepUninstalledPackagesBuilder add(PackageReference pkg) {
+ mPackages.add(pkg.packageName());
+ return this;
+ }
+
+ /**
+ * Add a collection of packages to the list of those which will not be cleaned up.
+ */
+ public KeepUninstalledPackagesBuilder add(Collection<PackageReference> packages) {
+ for (PackageReference pkg : packages) {
+ add(pkg);
+ }
+ return this;
+ }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java
index 5a49ae7..5602fb0 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java
@@ -22,7 +22,6 @@
import com.android.bedstead.nene.exceptions.NeneException;
import com.android.bedstead.nene.users.UserReference;
import com.android.bedstead.nene.utils.ShellCommand;
-import com.android.bedstead.nene.utils.ShellCommandUtils;
import java.io.File;
@@ -32,7 +31,6 @@
* <p>To resolve the package into a {@link Package}, see {@link #resolve()}.
*/
public abstract class PackageReference {
-
private final Packages mPackages;
private final String mPackageName;
@@ -69,8 +67,9 @@
// Expected output "Package X installed for user: Y"
ShellCommand.builderForUser(user, "pm install-existing")
.addOperand(mPackageName)
- .executeAndValidateOutput(
- (output) -> output.contains("installed for user"));
+ .validate(
+ (output) -> output.contains("installed for user"))
+ .execute();
return this;
} catch (AdbException e) {
throw new NeneException("Could not install-existing package " + this, e);
@@ -82,6 +81,8 @@
*
* <p>If this is the last user which has this package installed, then the package will no
* longer {@link #resolve()}.
+ *
+ * <p>If the package is not installed for the given user, nothing will happen.
*/
public PackageReference uninstall(UserReference user) {
if (user == null) {
@@ -91,7 +92,11 @@
// Expected output "Success"
ShellCommand.builderForUser(user, "pm uninstall")
.addOperand(mPackageName)
- .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+ .validate((output) -> {
+ output = output.toUpperCase();
+ return output.startsWith("SUCCESS") || output.contains("NOT INSTALLED FOR");
+ })
+ .execute();
return this;
} catch (AdbException e) {
throw new NeneException("Could not uninstall package " + this, e);
@@ -112,4 +117,12 @@
PackageReference other = (PackageReference) obj;
return other.mPackageName.equals(mPackageName);
}
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder("PackageReference{");
+ stringBuilder.append("packageName=" + mPackageName);
+ stringBuilder.append("}");
+ return stringBuilder.toString();
+ }
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
index a10aed5..22be828 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
@@ -20,7 +20,10 @@
import static com.android.bedstead.nene.users.User.UserState.RUNNING_UNLOCKED;
+import android.os.Build;
+
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.exceptions.AdbException;
@@ -30,6 +33,7 @@
import com.android.bedstead.nene.users.UserReference;
import com.android.bedstead.nene.utils.ShellCommand;
import com.android.bedstead.nene.utils.ShellCommandUtils;
+import com.android.bedstead.nene.utils.Versions;
import java.io.File;
import java.util.Collection;
@@ -98,14 +102,7 @@
if (user == null || apkFile == null) {
throw new NullPointerException();
}
- User resolvedUser = user.resolve();
- // TODO(scottjonathan): Consider if it's worth the additional call here - we could
- // optionally instead timeout the shell command (it doesn't respond if the user isn't
- // started)
- if (resolvedUser == null || resolvedUser.state() != RUNNING_UNLOCKED) {
- throw new NeneException("Packages can not be installed in non-started users "
- + "(Trying to install into user " + resolvedUser + ")");
- }
+ checkUserStartedBeforeInstall(user);
// By default when using ADB we don't know the package name of the file upon success.
// we could make an additional call to get it (either parsing all installed and finding the
@@ -118,24 +115,71 @@
ShellCommand.builderForUser(user, "pm install")
.addOperand("-r") // Reinstall automatically
.addOperand(apkFile.getAbsolutePath())
- .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+ .validate(ShellCommandUtils::startsWithSuccess)
+ .execute();
} catch (AdbException e) {
throw new NeneException("Could not install " + apkFile + " for user " + user, e);
}
}
+ /**
+ * Install an APK from the given byte array to a given {@link UserReference}.
+ *
+ * <p>The user must be started.
+ *
+ * <p>If the package is already installed, this will replace it.
+ */
+ public void install(UserReference user, byte[] apkFile) {
+ if (user == null || apkFile == null) {
+ throw new NullPointerException();
+ }
+
+ checkUserStartedBeforeInstall(user);
+
+ try {
+ // Expected output "Success"
+ ShellCommand.builderForUser(user, "pm install")
+ .addOption("-S", apkFile.length)
+ .addOperand("-r")
+ .writeToStdIn(apkFile)
+ .validate(ShellCommandUtils::startsWithSuccess)
+ .execute();
+ } catch (AdbException e) {
+ throw new NeneException("Could not install from bytes for user " + user, e);
+ }
+ }
+
+ private void checkUserStartedBeforeInstall(UserReference user) {
+ User resolvedUser = user.resolve();
+ // TODO(scottjonathan): Consider if it's worth the additional call here - we could
+ // optionally instead timeout the shell command (it doesn't respond if the user isn't
+ // started)
+ if (resolvedUser == null || resolvedUser.state() != RUNNING_UNLOCKED) {
+ throw new NeneException("Packages can not be installed in non-started users "
+ + "(Trying to install into user " + resolvedUser + ")");
+ }
+ }
+
+ /**
+ * Set packages which will not be cleaned up by the system even if they are not installed on
+ * any user.
+ *
+ * <p>This will ensure they can still be resolved and re-installed without needing the APK
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ public KeepUninstalledPackagesBuilder keepUninstalledPackages() {
+ Versions.requireS();
+
+ return new KeepUninstalledPackagesBuilder();
+ }
+
@Nullable
Package fetchPackage(String packageName) {
// TODO(scottjonathan): fillCache probably does more than we need here -
// can we make it more efficient?
fillCache();
- Package pkg = mCachedPackages.get(packageName);
- if (pkg == null || pkg.installedOnUsers().isEmpty()) {
- return null; // Treat it as uninstalled once all users are removed/removing
- }
-
- return pkg;
+ return mCachedPackages.get(packageName);
}
/**
@@ -154,7 +198,7 @@
private void fillCache() {
try {
// TODO: Replace use of adb on supported versions of Android
- String packageDumpsysOutput = ShellCommandUtils.executeCommand("dumpsys package");
+ String packageDumpsysOutput = ShellCommand.builder("dumpsys package").execute();
AdbPackageParser.ParseResult result = mParser.parse(packageDumpsysOutput);
mCachedPackages = result.mPackages;
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java
index 23f279b..91c7cb8 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java
@@ -32,6 +32,9 @@
interface AdbUserParser {
static AdbUserParser get(Users users, int sdkVersion) {
+ if (sdkVersion >= 31) {
+ return new AdbUserParser31(users);
+ }
if (sdkVersion >= 30) {
return new AdbUserParser30(users);
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java
index 470edf0..d9602bd 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java
@@ -88,7 +88,7 @@
public class AdbUserParser26 implements AdbUserParser {
static final int USER_LIST_BASE_INDENTATION = 2;
- private final Users mUsers;
+ final Users mUsers;
AdbUserParser26(Users users) {
if (users == null) {
@@ -132,6 +132,7 @@
String userInfo[] = userString.split("UserInfo\\{", 2)[1].split("\\}", 2)[0].split(":");
User.MutableUser user = new User.MutableUser();
user.mName = userInfo[1];
+ user.mFlags = Integer.parseInt(userInfo[2], 16);
user.mId = Integer.parseInt(userInfo[0]);
user.mSerialNo = Integer.parseInt(
userString.split("serialNo=", 2)[1].split("[ \n]", 2)[0]);
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser31.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser31.java
new file mode 100644
index 0000000..279c840
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser31.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.nene.users;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.bedstead.nene.exceptions.AdbParseException;
+
+/**
+ * Parser for "adb dumpsys user" on Android 30+
+ *
+ * <p>Example output:
+ * {@code
+ *
+ * }
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+// TODO(scottjonathan): Replace ADB calls for S with test apis
+public class AdbUserParser31 extends AdbUserParser30 {
+
+ AdbUserParser31(Users users) {
+ super(users);
+ }
+
+ @Override
+ User.MutableUser parseUser(String userString) throws AdbParseException {
+ User.MutableUser user = super.parseUser(userString);
+
+ if (user.mType.baseType().contains(UserType.BaseType.PROFILE)) {
+ try {
+ user.mParent = mUsers.find(
+ Integer.parseInt(userString.split("parentId=")[1].split("[ \n]")[0]));
+ } catch (IndexOutOfBoundsException e) {
+ throw new AdbParseException("Error parsing user", userString, e);
+ }
+ }
+
+ return user;
+ }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java
index d720740..4a75e7c 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java
@@ -16,11 +16,9 @@
package com.android.bedstead.nene.users;
-import android.os.Build;
import android.util.Log;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
/**
* Representation of a user on an Android device.
@@ -32,6 +30,9 @@
private static final String LOG_TAG = "User";
+ /* From UserInfo */
+ static final int FLAG_MANAGED_PROFILE = 0x00000020;
+
public enum UserState {
NOT_RUNNING,
RUNNING_LOCKED,
@@ -63,9 +64,11 @@
@Nullable Boolean mIsPrimary;
@Nullable UserState mState;
@Nullable Boolean mIsRemoving;
+ @Nullable Integer mFlags;
+ @Nullable UserReference mParent;
}
- private final MutableUser mMutableUser;
+ final MutableUser mMutableUser;
User(Users users, MutableUser mutableUser) {
super(users, mutableUser.mId);
@@ -94,10 +97,7 @@
/**
* Get the user type.
- *
- * <p>On Android versions < 11, this will return {@code null}.
*/
- @RequiresApi(Build.VERSION_CODES.R)
public UserType type() {
return mMutableUser.mType;
}
@@ -109,14 +109,28 @@
/**
* Return {@code true} if this is the primary user.
- *
- * <p>On Android versions < 11, this will return {@code null}.
*/
- @RequiresApi(Build.VERSION_CODES.R)
public Boolean isPrimary() {
return mMutableUser.mIsPrimary;
}
+ boolean hasFlag(int flag) {
+ if (mMutableUser.mFlags == null) {
+ return false;
+ }
+ return (mMutableUser.mFlags & flag) != 0;
+ }
+
+ /**
+ * Return the parent of this profile.
+ *
+ * <p>Returns {@code null} if this user is not a profile.
+ */
+ @Nullable
+ public UserReference parent() {
+ return mMutableUser.mParent;
+ }
+
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("User{");
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 4eef5ef..51bf36b 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
@@ -16,10 +16,13 @@
package com.android.bedstead.nene.users;
+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.Users.SYSTEM_USER_ID;
+
import android.os.Build;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import com.android.bedstead.nene.exceptions.AdbException;
import com.android.bedstead.nene.exceptions.NeneException;
@@ -36,6 +39,7 @@
private final Users mUsers;
private String mName;
private @Nullable UserType mType;
+ private @Nullable UserReference mParent;
UserBuilder(Users users) {
mUsers = users;
@@ -49,12 +53,31 @@
return this;
}
- @RequiresApi(Build.VERSION_CODES.R)
+ /**
+ * Set the {@link UserType}.
+ *
+ * <p>Defaults to android.os.usertype.full.SECONDARY
+ */
public UserBuilder type(UserType type) {
+ if (type == null) {
+ // We don't want to allow null to be passed in explicitly as that would cause subtle
+ // bugs when chaining with .supportedType() which can return null
+ throw new NullPointerException("Can not set type to null");
+ }
mType = type;
return this;
}
+ /**
+ * Set the parent of the new user.
+ *
+ * <p>This should only be set if the {@link #type(UserType)} is a profile.
+ */
+ public UserBuilder parent(UserReference parent) {
+ mParent = parent;
+ return this;
+ }
+
/** Create the user. */
public UserReference create() {
if (mName == null) {
@@ -64,16 +87,52 @@
ShellCommand.Builder commandBuilder = ShellCommand.builder("pm create-user");
if (mType != null) {
- commandBuilder.addOption("--user-type", mType.name());
+ if (mType.baseType().contains(UserType.BaseType.SYSTEM)) {
+ throw new NeneException(
+ "Can not create additional system users " + this);
+ }
+
+ if (mType.baseType().contains(UserType.BaseType.PROFILE)) {
+ if (mParent == null) {
+ throw new NeneException("When creating a profile, the parent user must be"
+ + " specified");
+ }
+
+ commandBuilder.addOption("--profileOf", mParent.id());
+ } else if (mParent != null) {
+ throw new NeneException("A parent should only be specified when create profiles");
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ if (mType.name().equals(MANAGED_PROFILE_TYPE_NAME)) {
+ if (mParent.id() != SYSTEM_USER_ID) {
+ // On R, this error will be thrown when we execute the command
+ throw new NeneException(
+ "Can not create managed profiles of users other than the "
+ + "system user"
+ );
+ }
+
+ commandBuilder.addOperand("--managed");
+ } else if (!mType.name().equals(SECONDARY_USER_TYPE_NAME)) {
+ // This shouldn't be reachable as before R we can't fetch a list of user types
+ // so the only supported ones are system/managed profile/secondary
+ throw new NeneException(
+ "Can not create users of type " + mType + " on this device");
+ }
+ } else {
+ commandBuilder.addOption("--user-type", mType.name());
+ }
}
commandBuilder.addOperand(mName);
// Expected success string is e.g. "Success: created user id 14"
try {
- int userId = Integer.parseInt(
- commandBuilder.executeAndValidateOutput(ShellCommandUtils::startsWithSuccess)
- .split("id ")[1].trim());
+ int userId =
+ commandBuilder.validate(ShellCommandUtils::startsWithSuccess)
+ .executeAndParseOutput(
+ (output) -> Integer.parseInt(output.split("id ")[1].trim()));
return new UnresolvedUser(mUsers, 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 5c21aef..47ed133 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
@@ -85,7 +85,8 @@
// Expected success string is "Success: removed user"
ShellCommand.builder("pm remove-user")
.addOperand(mId)
- .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+ .validate(ShellCommandUtils::startsWithSuccess)
+ .execute();
mUsers.waitForUserToNotExistOrMatch(this, User::isRemoving);
} catch (AdbException e) {
throw new NeneException("Could not remove user + " + this, e);
@@ -108,7 +109,8 @@
ShellCommand.builder("am start-user")
.addOperand(mId)
.addOperand("-w")
- .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+ .validate(ShellCommandUtils::startsWithSuccess)
+ .execute();
User waitedUser = mUsers.waitForUserToNotExistOrMatch(
this, (user) -> user.state() == UserState.RUNNING_UNLOCKED);
if (waitedUser == null) {
@@ -128,11 +130,13 @@
*/
public UserReference stop() {
try {
- // Expects no output on success or failure
+ // Expects no output on success or failure - stderr output on failure
ShellCommand.builder("am stop-user")
+ .addOperand("-f") // Force stop
.addOperand(mId)
.allowEmptyOutput(true)
- .executeAndValidateOutput(ShellCommandUtils::doesNotStartWithError);
+ .validate(String::isEmpty)
+ .execute();
User waitedUser = mUsers.waitForUserToNotExistOrMatch(
this, (user) -> user.state() == UserState.NOT_RUNNING);
if (waitedUser == null) {
@@ -164,7 +168,8 @@
ShellCommand.builder("am switch-user")
.addOperand(mId)
.allowEmptyOutput(true)
- .executeAndValidateOutput(String::isEmpty);
+ .validate(String::isEmpty)
+ .execute();
broadcastReceiver.awaitForBroadcast();
} catch (AdbException e) {
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java
index 17121fe3..33cdef3 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java
@@ -16,20 +16,17 @@
package com.android.bedstead.nene.users;
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
-
import java.util.Set;
/**
* Represents information about an Android User type.
- *
- * <p>Only supported on Android 11 and above.
*/
-@RequiresApi(Build.VERSION_CODES.R)
public final class UserType {
+ public static final String SECONDARY_USER_TYPE_NAME = "android.os.usertype.full.SECONDARY";
+ public static final String SYSTEM_USER_TYPE_NAME = "android.os.usertype.full.SYSTEM";
+ public static final String MANAGED_PROFILE_TYPE_NAME = "android.os.usertype.profile.MANAGED";
+
public static final int UNLIMITED = -1;
public enum BaseType {
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 7f52a42..d64c88fd 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
@@ -19,29 +19,38 @@
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Process.myUserHandle;
+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;
+
import android.os.Build;
import android.os.UserHandle;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import com.android.bedstead.nene.exceptions.AdbException;
import com.android.bedstead.nene.exceptions.AdbParseException;
import com.android.bedstead.nene.exceptions.NeneException;
-import com.android.bedstead.nene.utils.ShellCommandUtils;
+import com.android.bedstead.nene.utils.ShellCommand;
import com.android.compatibility.common.util.PollingCheck;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
public final class Users {
+ static final int SYSTEM_USER_ID = 0;
private static final long WAIT_FOR_USER_TIMEOUT_MS = 1000 * 60;
private Map<Integer, User> mCachedUsers = null;
private Map<String, UserType> mCachedUserTypes = null;
+ private Set<UserType> mCachedUserTypeValues = null;
private final AdbUserParser parser = AdbUserParser.get(this, SDK_INT);
/** Get all {@link User}s on the device. */
@@ -81,33 +90,66 @@
}
/** Get all supported {@link UserType}s. */
- @RequiresApi(Build.VERSION_CODES.R)
- @Nullable
- public Collection<UserType> supportedTypes() {
- if (SDK_INT < Build.VERSION_CODES.R) {
- return null;
- }
- if (mCachedUserTypes == null) {
- // supportedTypes cannot change so we don't need to refill the cache
- fillCache();
- }
- return mCachedUserTypes.values();
+ public Set<UserType> supportedTypes() {
+ ensureSupportedTypesCacheFilled();
+ return mCachedUserTypeValues;
}
/** Get a {@link UserType} with the given {@code typeName}, or {@code null} */
- @RequiresApi(Build.VERSION_CODES.R)
- @Nullable
public UserType supportedType(String typeName) {
- if (SDK_INT < Build.VERSION_CODES.R) {
- return null;
- }
- if (mCachedUserTypes == null) {
- // supportedTypes cannot change so we don't need to refill the cache
- fillCache();
- }
+ ensureSupportedTypesCacheFilled();
return mCachedUserTypes.get(typeName);
}
+ private void ensureSupportedTypesCacheFilled() {
+ if (mCachedUserTypes != null) {
+ // SupportedTypes don't change so don't need to be refreshed
+ return;
+ }
+ if (SDK_INT < Build.VERSION_CODES.R) {
+ mCachedUserTypes = new HashMap<>();
+ mCachedUserTypes.put(MANAGED_PROFILE_TYPE_NAME, managedProfileUserType());
+ mCachedUserTypes.put(SYSTEM_USER_TYPE_NAME, systemUserType());
+ mCachedUserTypes.put(SECONDARY_USER_TYPE_NAME, secondaryUserType());
+ mCachedUserTypeValues = new HashSet<>();
+ mCachedUserTypeValues.addAll(mCachedUserTypes.values());
+ return;
+ }
+
+ fillCache();
+ }
+
+ private UserType managedProfileUserType() {
+ UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType();
+ managedProfileMutableUserType.mName = MANAGED_PROFILE_TYPE_NAME;
+ managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.PROFILE);
+ managedProfileMutableUserType.mEnabled = true;
+ managedProfileMutableUserType.mMaxAllowed = -1;
+ managedProfileMutableUserType.mMaxAllowedPerParent = 1;
+ return new UserType(managedProfileMutableUserType);
+ }
+
+ private UserType systemUserType() {
+ UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType();
+ managedProfileMutableUserType.mName = SYSTEM_USER_TYPE_NAME;
+ managedProfileMutableUserType.mBaseType =
+ Set.of(UserType.BaseType.FULL, UserType.BaseType.SYSTEM);
+ managedProfileMutableUserType.mEnabled = true;
+ managedProfileMutableUserType.mMaxAllowed = -1;
+ managedProfileMutableUserType.mMaxAllowedPerParent = -1;
+ return new UserType(managedProfileMutableUserType);
+ }
+
+ private UserType secondaryUserType() {
+ UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType();
+ managedProfileMutableUserType.mName = SECONDARY_USER_TYPE_NAME;
+ managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.FULL);
+ managedProfileMutableUserType.mEnabled = true;
+ managedProfileMutableUserType.mMaxAllowed = -1;
+ managedProfileMutableUserType.mMaxAllowedPerParent = -1;
+ return new UserType(managedProfileMutableUserType);
+ }
+
/**
* Create a new user.
*/
@@ -118,15 +160,55 @@
private void fillCache() {
try {
// TODO: Replace use of adb on supported versions of Android
- String userDumpsysOutput = ShellCommandUtils.executeCommand("dumpsys user");
+ String userDumpsysOutput = ShellCommand.builder("dumpsys user").execute();
AdbUserParser.ParseResult result = parser.parse(userDumpsysOutput);
mCachedUsers = result.mUsers;
- mCachedUserTypes = result.mUserTypes;
+ if (result.mUserTypes != null) {
+ mCachedUserTypes = result.mUserTypes;
+ } else {
+ ensureSupportedTypesCacheFilled();
+ }
- // We don't expose users who are currently being removed
- mCachedUsers.entrySet().removeIf(
- integerUserEntry -> integerUserEntry.getValue().isRemoving());
+ Iterator<Map.Entry<Integer, User>> iterator = mCachedUsers.entrySet().iterator();
+
+ while (iterator.hasNext()) {
+ Map.Entry<Integer, User> entry = iterator.next();
+
+ if (entry.getValue().isRemoving()) {
+ // We don't expose users who are currently being removed
+ iterator.remove();
+ continue;
+ }
+
+ User.MutableUser mutableUser = entry.getValue().mMutableUser;
+
+ if (SDK_INT < Build.VERSION_CODES.R) {
+ if (entry.getValue().id() == SYSTEM_USER_ID) {
+ mutableUser.mType = supportedType(SYSTEM_USER_TYPE_NAME);
+ mutableUser.mIsPrimary = true;
+ } else if (entry.getValue().hasFlag(User.FLAG_MANAGED_PROFILE)) {
+ mutableUser.mType =
+ supportedType(MANAGED_PROFILE_TYPE_NAME);
+ mutableUser.mIsPrimary = false;
+ } else {
+ mutableUser.mType =
+ supportedType(SECONDARY_USER_TYPE_NAME);
+ mutableUser.mIsPrimary = false;
+ }
+ }
+
+ if (SDK_INT < Build.VERSION_CODES.S) {
+ if (mutableUser.mType.baseType()
+ .contains(UserType.BaseType.PROFILE)) {
+ // We assume that all profiles before S were on the System User
+ mutableUser.mParent = find(SYSTEM_USER_ID);
+ }
+ }
+ }
+
+ mCachedUserTypeValues = new HashSet<>();
+ mCachedUserTypeValues.addAll(mCachedUserTypes.values());
} catch (AdbException | AdbParseException e) {
throw new RuntimeException("Error filling cache", e);
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 0ca0044..6a9a499 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
@@ -28,6 +28,10 @@
*/
public final class ShellCommand {
+ // 60 seconds
+ private static final int MAX_WAIT_UNTIL_ATTEMPTS = 600;
+ private static final long WAIT_UNTIL_DELAY_MILLIS = 100;
+
public static Builder builder(String command) {
if (command == null) {
throw new NullPointerException();
@@ -49,8 +53,12 @@
public static final class Builder {
private final StringBuilder commandBuilder;
+ @Nullable
private byte[] mStdInBytes = null;
+ @Nullable
private boolean mAllowEmptyOutput = false;
+ @Nullable
+ private Function<String, Boolean> mOutputSuccessChecker = null;
private Builder(String command) {
commandBuilder = new StringBuilder(command);
@@ -87,6 +95,24 @@
}
/**
+ * Write the given {@code stdIn} to standard in.
+ */
+ public Builder writeToStdIn(byte[] stdIn) {
+ mStdInBytes = stdIn;
+ return this;
+ }
+
+ /**
+ * Validate the output when executing.
+ *
+ * <p>{@code outputSuccessChecker} should return {@code true} if the output is valid.
+ */
+ public Builder validate(Function<String, Boolean> outputSuccessChecker) {
+ mOutputSuccessChecker = outputSuccessChecker;
+ return this;
+ }
+
+ /**
* Build the full command including all options and operands.
*/
public String build() {
@@ -95,18 +121,56 @@
/** See {@link ShellCommandUtils#executeCommand(java.lang.String)}. */
public String execute() throws AdbException {
+ if (mOutputSuccessChecker != null) {
+ return ShellCommandUtils.executeCommandAndValidateOutput(
+ commandBuilder.toString(),
+ /* allowEmptyOutput= */ mAllowEmptyOutput,
+ mStdInBytes,
+ mOutputSuccessChecker);
+ }
+
return ShellCommandUtils.executeCommand(
commandBuilder.toString(),
- /* allowEmptyOutput= */ mAllowEmptyOutput);
+ /* allowEmptyOutput= */ mAllowEmptyOutput,
+ mStdInBytes);
}
- /** See {@link ShellCommandUtils#executeCommandAndValidateOutput(String, Function)}. */
- public String executeAndValidateOutput(Function<String, Boolean> outputSuccessChecker)
- throws AdbException {
- return ShellCommandUtils.executeCommandAndValidateOutput(
- commandBuilder.toString(),
- /* allowEmptyOutput= */ mAllowEmptyOutput,
- outputSuccessChecker);
+ /**
+ * See {@link #execute} and then extract information from the output using
+ * {@code outputParser}.
+ *
+ * <p>If any {@link Exception} is thrown by {@code outputParser}, and {@link AdbException}
+ * will be thrown.
+ */
+ public <E> E executeAndParseOutput(Function<String, E> outputParser) throws AdbException {
+ String output = execute();
+
+ try {
+ return outputParser.apply(output);
+ } catch (RuntimeException e) {
+ throw new AdbException(
+ "Could not parse output", commandBuilder.toString(), output, e);
+ }
+ }
+
+ /**
+ * Execute the command and check that the output meets a given criteria. Run the
+ * command repeatedly until the output meets the criteria.
+ *
+ * <p>{@code outputSuccessChecker} should return {@code true} if the output indicates the
+ * command executed successfully.
+ */
+ public String executeUntilValid() throws InterruptedException, AdbException {
+ int attempts = 0;
+ while (attempts++ < MAX_WAIT_UNTIL_ATTEMPTS) {
+ try {
+ return execute();
+ } catch (AdbException e) {
+ // ignore, will retry
+ Thread.sleep(WAIT_UNTIL_DELAY_MILLIS);
+ }
+ }
+ return execute();
}
}
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
index d9c7da7..60ebb4f 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
@@ -18,9 +18,18 @@
import static android.os.Build.VERSION.SDK_INT;
-import com.android.bedstead.nene.exceptions.AdbException;
-import com.android.compatibility.common.util.SystemUtil;
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.compatibility.common.util.FileUtils;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.function.Function;
/**
@@ -28,9 +37,11 @@
*/
public final class ShellCommandUtils {
- // 60 seconds
- private static final int MAX_WAIT_UNTIL_ATTEMPTS = 600;
- private static final long WAIT_UNTIL_DELAY_MILLIS = 100;
+ private static final String LOG_TAG = ShellCommandUtils.class.getName();
+
+ private static final int OUT_DESCRIPTOR_INDEX = 0;
+ private static final int IN_DESCRIPTOR_INDEX = 1;
+ private static final int ERR_DESCRIPTOR_INDEX = 2;
private ShellCommandUtils() { }
@@ -45,24 +56,56 @@
*
* <p>Callers should be careful to check the command's output is valid.
*/
- public static String executeCommand(String command) throws AdbException {
- return executeCommand(command, /* allowEmptyOutput=*/ false);
+ static String executeCommand(String command) throws AdbException {
+ return executeCommand(command, /* allowEmptyOutput=*/ false, /* stdInBytes= */ null);
}
- static String executeCommand(String command, boolean allowEmptyOutput)
+ static String executeCommand(String command, boolean allowEmptyOutput, byte[] stdInBytes)
throws AdbException {
- if (SDK_INT < 31) { // TODO(scottjonathan): Replace with S
- return executeCommandPreS(command, allowEmptyOutput);
+ logCommand(command, allowEmptyOutput, stdInBytes);
+
+ if (SDK_INT < Versions.S) {
+ return executeCommandPreS(command, allowEmptyOutput, stdInBytes);
}
// TODO(scottjonathan): Add argument to force errors to stderr
+ UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
- return SystemUtil.runShellCommandOrThrow(command);
- } catch (AssertionError e) {
- throw new AdbException("Error executing command", command, /* output= */ null, e);
+
+ ParcelFileDescriptor[] fds = automation.executeShellCommandRwe(command);
+ ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX];
+ ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX];
+ ParcelFileDescriptor fdErr = fds[ERR_DESCRIPTOR_INDEX];
+
+ writeStdInAndClose(fdIn, stdInBytes);
+
+ String out = readStreamAndClose(fdOut);
+ String err = readStreamAndClose(fdErr);
+
+ if (!err.isEmpty()) {
+ throw new AdbException("Error executing command", command, out, err);
+ }
+
+ Log.d(LOG_TAG, "Command result: " + out);
+
+ return out;
+ } catch (IOException e) {
+ throw new AdbException("Error executing command", command, e);
}
}
+ private static void logCommand(String command, boolean allowEmptyOutput, byte[] stdInBytes) {
+ StringBuilder logBuilder = new StringBuilder("Executing shell command ");
+ logBuilder.append(command);
+ if (allowEmptyOutput) {
+ logBuilder.append(" (allow empty output)");
+ }
+ if (stdInBytes != null) {
+ logBuilder.append(" (writing to stdIn)");
+ }
+ Log.d(LOG_TAG, logBuilder.toString());
+ }
+
/**
* Execute an adb shell command and check that the output meets a given criteria.
*
@@ -75,18 +118,20 @@
* <p>{@code outputSuccessChecker} should return {@code true} if the output indicates the
* command executed successfully.
*/
- public static String executeCommandAndValidateOutput(
+ static String executeCommandAndValidateOutput(
String command, Function<String, Boolean> outputSuccessChecker) throws AdbException {
return executeCommandAndValidateOutput(command,
/* allowEmptyOutput= */ false,
+ /* stdInBytes= */ null,
outputSuccessChecker);
}
static String executeCommandAndValidateOutput(
String command,
boolean allowEmptyOutput,
+ byte[] stdInBytes,
Function<String, Boolean> outputSuccessChecker) throws AdbException {
- String output = executeCommand(command, allowEmptyOutput);
+ String output = executeCommand(command, allowEmptyOutput, stdInBytes);
if (!outputSuccessChecker.apply(output)) {
throw new AdbException("Command did not meet success criteria", command, output);
}
@@ -94,34 +139,6 @@
}
/**
- * Execute an adb shell command and check that the output meets a given criteria. Run the
- * command repeatedly until the output meets the criteria.
- *
- * <p>On S and above, any output printed to standard error will result in an exception and the
- * {@code outputSuccessChecker} not being called. Empty output will still be processed.
- *
- * <p>Prior to S, if there is no output on standard out, regardless of if there is output on
- * standard error, {@code outputSuccessChecker} will not be called.
- *
- * <p>{@code outputSuccessChecker} should return {@code true} if the output indicates the
- * command executed successfully.
- */
- public static String executeCommandUntilOutputValid(
- String command, Function<String, Boolean> outputSuccessChecker)
- throws AdbException, InterruptedException {
- int attempts = 0;
- while (attempts++ < MAX_WAIT_UNTIL_ATTEMPTS) {
- try {
- return executeCommandAndValidateOutput(command, outputSuccessChecker);
- } catch (AdbException e) {
- // ignore, will retry
- Thread.sleep(WAIT_UNTIL_DELAY_MILLIS);
- }
- }
- return executeCommandAndValidateOutput(command, outputSuccessChecker);
- }
-
- /**
* Return {@code true} if {@code output} starts with "success", case insensitive.
*/
public static boolean startsWithSuccess(String output) {
@@ -136,15 +153,48 @@
}
private static String executeCommandPreS(
- String command, boolean allowEmptyOutput) throws AdbException {
+ String command, boolean allowEmptyOutput, byte[] stdInBytes) throws AdbException {
+ UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ ParcelFileDescriptor[] fds = automation.executeShellCommandRw(command);
+ ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX];
+ ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX];
- String result = SystemUtil.runShellCommand(command);
+ try {
+ writeStdInAndClose(fdIn, stdInBytes);
- if (!allowEmptyOutput && result.isEmpty()) {
+ try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) {
+ String out = new String(FileUtils.readInputStreamFully(fis));
+
+ if (!allowEmptyOutput && out.isEmpty()) {
+ throw new AdbException(
+ "No output from command. There's likely an error on stderr",
+ command, out);
+ }
+
+ Log.d(LOG_TAG, "Command result: " + out);
+
+ return out;
+ }
+ } catch (IOException e) {
throw new AdbException(
- "No output from command. There's likely an error on stderr", command, result);
+ "Error reading command output", command, e);
}
+ }
- return result;
+ private static void writeStdInAndClose(ParcelFileDescriptor fdIn, byte[] stdInBytes)
+ throws IOException {
+ if (stdInBytes != null) {
+ try (FileOutputStream fos = new ParcelFileDescriptor.AutoCloseOutputStream(fdIn)) {
+ fos.write(stdInBytes);
+ }
+ } else {
+ fdIn.close();
+ }
+ }
+
+ private static String readStreamAndClose(ParcelFileDescriptor fd) throws IOException {
+ try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
+ return new String(FileUtils.readInputStreamFully(fis));
+ }
}
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/Versions.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/Versions.java
new file mode 100644
index 0000000..bc33792
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/Versions.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.nene.utils;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+import android.os.Build;
+
+/** Version constants used when VERSION_CODES is not final. */
+public final class Versions {
+ // TODO(scottjonathan): Replace once S version is final
+ public static final int S = 31;
+
+ private Versions() {
+
+ }
+
+ /** Require that this is running on Android S or above. */
+ public static void requireS() {
+ if (!isRunningOn(S, "S")) {
+ throw new UnsupportedOperationException(
+ "keepUninstalledPackages is only available on S+ (currently "
+ + Build.VERSION.CODENAME + ")");
+ }
+ }
+
+ /** True if the app is running on the given Android version or above. */
+ public static boolean isRunningOn(int version, String codename) {
+ return (SDK_INT >= version || Build.VERSION.CODENAME.equals(codename));
+ }
+}
diff --git a/common/device-side/bedstead/nene/src/test/AndroidManifest.xml b/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
index 8fd18c6..a82d39b 100644
--- a/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
+++ b/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
@@ -27,7 +27,7 @@
<activity android:name="com.android.bedstead.nene.test.Activity" android:exported="false"/>
</application>
- <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.bedstead.nene.test"
android:label="Nene Tests" />
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.java
index 5cbde10..187d8df 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.java
@@ -38,6 +38,8 @@
private static final String NON_EXISTING_PACKAGE_NAME = "com.package.does.not.exist";
private static final String PACKAGE_NAME = NON_EXISTING_PACKAGE_NAME;
private static final String EXISTING_PACKAGE_NAME = "com.android.providers.telephony";
+ private final PackageReference mTestAppReference =
+ mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
// Controlled by AndroidTest.xml
private static final String TEST_APP_PACKAGE_NAME =
@@ -80,33 +82,52 @@
}
@Test
- public void uninstall_packageIsUninstalled() {
- mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
- PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
-
- packageReference.uninstall(mUser);
-
- assertThat(packageReference.resolve()).isNull();
- }
-
- @Test
- public void uninstall_packageNotInstalledForUser_throwsException() {
+ public void uninstall_packageIsInstalledForDifferentUser_isUninstalledForUser() {
UserReference otherUser = mTestApis.users().createUser().createAndStart();
- mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
- PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
try {
- assertThrows(NeneException.class, () -> packageReference.uninstall(otherUser));
+ mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+ mTestApis.packages().install(otherUser, TEST_APP_APK_FILE);
+
+ mTestAppReference.uninstall(mUser);
+
+ assertThat(mTestAppReference.resolve().installedOnUsers()).containsExactly(otherUser);
} finally {
- packageReference.uninstall(mUser);
otherUser.remove();
}
}
@Test
- public void uninstall_packageDoesNotExist_throwsException() {
+ public void uninstall_packageIsUninstalled() {
+ mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+
+ mTestAppReference.uninstall(mUser);
+
+ // Depending on when Android cleans up the users, this may either no longer resolve or
+ // just have an empty user list
+ Package pkg = mTestAppReference.resolve();
+ if (pkg != null) {
+ assertThat(pkg.installedOnUsers()).isEmpty();
+ }
+ }
+
+ @Test
+ public void uninstall_packageNotInstalledForUser_doesNotThrowException() {
+ UserReference otherUser = mTestApis.users().createUser().createAndStart();
+ mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+
+ try {
+ mTestAppReference.uninstall(otherUser);
+ } finally {
+ mTestAppReference.uninstall(mUser);
+ otherUser.remove();
+ }
+ }
+
+ @Test
+ public void uninstall_packageDoesNotExist_doesNotThrowException() {
PackageReference packageReference = mTestApis.packages().find(NON_EXISTING_PACKAGE_NAME);
- assertThrows(NeneException.class, () -> packageReference.uninstall(mUser));
+ packageReference.uninstall(mUser);
}
}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java
index bd09b7e7..07e8a45 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java
@@ -18,17 +18,23 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.exceptions.NeneException;
import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.Versions;
+import com.android.compatibility.common.util.FileUtils;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
@RunWith(JUnit4.class)
public class PackagesTest {
@@ -39,14 +45,27 @@
private static final String TEST_APP_PACKAGE_NAME =
"com.android.bedstead.nene.testapps.TestApp1";
private static final File TEST_APP_APK_FILE = new File("/data/local/tmp/NeneTestApp1.apk");
+ private static final byte[] TEST_APP_BYTES = loadBytes(TEST_APP_APK_FILE);
private final TestApis mTestApis = new TestApis();
private final UserReference mUser = mTestApis.users().instrumented();
private final PackageReference mExistingPackage =
mTestApis.packages().find("com.android.providers.telephony");
+ private final PackageReference mTestAppReference =
+ mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+ private final PackageReference mDifferentTestAppReference =
+ mTestApis.packages().find(NON_EXISTING_PACKAGE);
private final UserReference mNonExistingUser = mTestApis.users().find(99999);
private final File mApkFile = new File("");
+ private static byte[] loadBytes(File file) {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ return FileUtils.readInputStreamFully(fis);
+ } catch (IOException e) {
+ throw new AssertionError("Could not read file bytes");
+ }
+ }
+
@Test
public void construct_nullTestApis_throwsException() {
assertThrows(NullPointerException.class, () -> new Packages(/* testApis= */ null));
@@ -91,12 +110,11 @@
@Test
public void installedForUser_containsPackageInstalledForUser() {
mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
- PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
try {
- assertThat(mTestApis.packages().installedForUser(mUser)).contains(packageReference);
+ assertThat(mTestApis.packages().installedForUser(mUser)).contains(mTestAppReference);
} finally {
- packageReference.uninstall(mUser);
+ mTestAppReference.uninstall(mUser);
}
}
@@ -104,13 +122,12 @@
public void installedForUser_doesNotContainPackageNotInstalledForUser() {
UserReference otherUser = mTestApis.users().createUser().create();
mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
- PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
try {
assertThat(mTestApis.packages().installedForUser(otherUser))
- .doesNotContain(packageReference);
+ .doesNotContain(mTestAppReference);
} finally {
- packageReference.uninstall(mUser);
+ mTestAppReference.uninstall(mUser);
otherUser.remove();
}
}
@@ -122,21 +139,44 @@
}
@Test
+ public void install_byteArray_nullUser_throwsException() {
+ assertThrows(NullPointerException.class,
+ () -> mTestApis.packages().install(/* user= */ null, TEST_APP_BYTES));
+ }
+
+ @Test
public void install_nullApkFile_throwsException() {
assertThrows(NullPointerException.class,
() -> mTestApis.packages().install(mUser, (File) /* apkFile= */ null));
}
@Test
+ public void install_nullByteArray_throwsException() {
+ assertThrows(NullPointerException.class,
+ () -> mTestApis.packages().install(mUser, (byte[]) /* apkFile= */ null));
+ }
+
+ @Test
public void install_instrumentedUser_isInstalled() {
mTestApis.packages().install(mTestApis.users().instrumented(), TEST_APP_APK_FILE);
- PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
try {
- assertThat(packageReference.resolve().installedOnUsers())
+ assertThat(mTestAppReference.resolve().installedOnUsers())
.contains(mTestApis.users().instrumented());
} finally {
- packageReference.uninstall(mTestApis.users().instrumented());
+ mTestAppReference.uninstall(mTestApis.users().instrumented());
+ }
+ }
+
+ @Test
+ public void install_byteArray_instrumentedUser_isInstalled() {
+ mTestApis.packages().install(mTestApis.users().instrumented(), TEST_APP_BYTES);
+
+ try {
+ assertThat(mTestAppReference.resolve().installedOnUsers())
+ .contains(mTestApis.users().instrumented());
+ } finally {
+ mTestAppReference.uninstall(mTestApis.users().instrumented());
}
}
@@ -144,10 +184,21 @@
public void install_differentUser_isInstalled() {
UserReference user = mTestApis.users().createUser().createAndStart();
mTestApis.packages().install(user, TEST_APP_APK_FILE);
- PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
try {
- assertThat(packageReference.resolve().installedOnUsers()).contains(user);
+ assertThat(mTestAppReference.resolve().installedOnUsers()).contains(user);
+ } finally {
+ user.remove();
+ }
+ }
+
+ @Test
+ public void install_byteArray_differentUser_isInstalled() {
+ UserReference user = mTestApis.users().createUser().createAndStart();
+ mTestApis.packages().install(user, TEST_APP_BYTES);
+
+ try {
+ assertThat(mTestAppReference.resolve().installedOnUsers()).contains(user);
} finally {
user.remove();
}
@@ -166,37 +217,162 @@
}
@Test
+ public void install_byteArray_userNotStarted_throwsException() {
+ UserReference user = mTestApis.users().createUser().create().stop();
+
+ try {
+ assertThrows(NeneException.class, () -> mTestApis.packages().install(user,
+ TEST_APP_BYTES));
+ } finally {
+ user.remove();
+ }
+ }
+
+ @Test
public void install_userDoesNotExist_throwsException() {
assertThrows(NeneException.class, () -> mTestApis.packages().install(mNonExistingUser,
TEST_APP_APK_FILE));
}
@Test
+ public void install_byteArray_userDoesNotExist_throwsException() {
+ assertThrows(NeneException.class, () -> mTestApis.packages().install(mNonExistingUser,
+ TEST_APP_BYTES));
+ }
+
+ @Test
public void install_alreadyInstalledForUser_installs() {
mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
- PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
try {
mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
- assertThat(packageReference.resolve().installedOnUsers()).contains(mUser);
+ assertThat(mTestAppReference.resolve().installedOnUsers()).contains(mUser);
} finally {
- packageReference.uninstall(mUser);
+ mTestAppReference.uninstall(mUser);
+ }
+ }
+
+ @Test
+ public void install_byteArray_alreadyInstalledForUser_installs() {
+ mTestApis.packages().install(mUser, TEST_APP_BYTES);
+
+ try {
+ mTestApis.packages().install(mUser, TEST_APP_BYTES);
+ assertThat(mTestAppReference.resolve().installedOnUsers()).contains(mUser);
+ } finally {
+ mTestAppReference.uninstall(mUser);
}
}
@Test
public void install_alreadyInstalledOnOtherUser_installs() {
UserReference otherUser = mTestApis.users().createUser().createAndStart();
- PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
try {
mTestApis.packages().install(otherUser, TEST_APP_APK_FILE);
mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
- assertThat(packageReference.resolve().installedOnUsers()).contains(mUser);
+ assertThat(mTestAppReference.resolve().installedOnUsers()).contains(mUser);
} finally {
- packageReference.uninstall(mUser);
+ mTestAppReference.uninstall(mUser);
otherUser.remove();
}
}
+
+ @Test
+ public void install_byteArray_alreadyInstalledOnOtherUser_installs() {
+ UserReference otherUser = mTestApis.users().createUser().createAndStart();
+ try {
+ mTestApis.packages().install(otherUser, TEST_APP_BYTES);
+
+ mTestApis.packages().install(mUser, TEST_APP_BYTES);
+
+ assertThat(mTestAppReference.resolve().installedOnUsers()).contains(mUser);
+ } finally {
+ mTestAppReference.uninstall(mUser);
+ otherUser.remove();
+ }
+ }
+
+ @Test
+ public void keepUninstalledPackages_packageIsUninstalled_packageStillResolves() {
+ assumeTrue("keepUninstalledPackages is only supported on S+",
+ Versions.isRunningOn(Versions.S, "S"));
+
+ mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+ mTestApis.packages().keepUninstalledPackages()
+ .add(mTestAppReference)
+ .commit();
+
+ try {
+ mTestAppReference.uninstall(mUser);
+
+ assertThat(mTestAppReference.resolve()).isNotNull();
+ } finally {
+ mTestApis.packages().keepUninstalledPackages().clear();
+ }
+ }
+
+ @Test
+ @Ignore("While using adb calls this is not reliable, enable once we use framework calls for uninstall")
+ public void keepUninstalledPackages_packageRemovedFromList_packageIsUninstalled_packageDoesNotResolve() {
+ assumeTrue("keepUninstalledPackages is only supported on S+",
+ Versions.isRunningOn(Versions.S, "S"));
+
+ mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+ mTestApis.packages().keepUninstalledPackages()
+ .add(mTestAppReference)
+ .commit();
+ mTestApis.packages().keepUninstalledPackages()
+ .add(mDifferentTestAppReference)
+ .commit();
+
+ try {
+ mTestAppReference.uninstall(mUser);
+
+ assertThat(mTestAppReference.resolve()).isNull();
+ } finally {
+ mTestApis.packages().keepUninstalledPackages().clear();
+ }
+ }
+
+ @Test
+ @Ignore("While using adb calls this is not reliable, enable once we use framework calls for uninstall")
+ public void keepUninstalledPackages_cleared_packageIsUninstalled_packageDoesNotResolve() {
+ assumeTrue("keepUninstalledPackages is only supported on S+",
+ Versions.isRunningOn(Versions.S, "S"));
+
+ mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+
+ mTestApis.packages().keepUninstalledPackages()
+ .add(mTestAppReference)
+ .commit();
+ mTestApis.packages().keepUninstalledPackages().clear();
+
+ try {
+ mTestAppReference.uninstall(mUser);
+
+ assertThat(mTestAppReference.resolve()).isNull();
+ } finally {
+ mTestApis.packages().keepUninstalledPackages().clear();
+ }
+ }
+
+ @Test
+ @Ignore("While using adb calls this is not reliable, enable once we use framework calls for uninstall")
+ public void keepUninstalledPackages_packageRemovedFromList_packageAlreadyUninstalled_packageDoesNotResolve() {
+ assumeTrue("keepUninstalledPackages is only supported on S+",
+ Versions.isRunningOn(Versions.S, "S"));
+
+ mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+ mTestApis.packages().keepUninstalledPackages().add(mTestAppReference).commit();
+ mTestAppReference.uninstall(mUser);
+ mTestApis.packages().keepUninstalledPackages().add(mDifferentTestAppReference).commit();
+
+ try {
+ assertThat(mTestAppReference.resolve()).isNull();
+ } finally {
+ mTestApis.packages().keepUninstalledPackages().clear();
+ }
+ }
}
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 4d143d6..3ac74b0 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
@@ -51,26 +51,25 @@
private static final String TEST_ACTIVITY_NAME = "com.android.bedstead.nene.test.Activity";
private final TestApis mTestApis = new TestApis();
- private final Users mUsers = mTestApis.users();
@Test
public void id_returnsId() {
- assertThat(mUsers.find(USER_ID).id()).isEqualTo(USER_ID);
+ assertThat(mTestApis.users().find(USER_ID).id()).isEqualTo(USER_ID);
}
@Test
public void userHandle_referencesId() {
- assertThat(mUsers.find(USER_ID).userHandle().getIdentifier()).isEqualTo(USER_ID);
+ assertThat(mTestApis.users().find(USER_ID).userHandle().getIdentifier()).isEqualTo(USER_ID);
}
@Test
public void resolve_doesNotExist_returnsNull() {
- assertThat(mUsers.find(NON_EXISTING_USER_ID).resolve()).isNull();
+ assertThat(mTestApis.users().find(NON_EXISTING_USER_ID).resolve()).isNull();
}
@Test
public void resolve_doesExist_returnsUser() {
- UserReference userReference = mUsers.createUser().create();
+ UserReference userReference = mTestApis.users().createUser().create();
try {
assertThat(userReference.resolve()).isNotNull();
@@ -81,7 +80,7 @@
@Test
public void resolve_doesExist_userHasCorrectDetails() {
- UserReference userReference = mUsers.createUser().name(USER_NAME).create();
+ UserReference userReference = mTestApis.users().createUser().name(USER_NAME).create();
try {
User user = userReference.resolve();
@@ -93,26 +92,27 @@
@Test
public void remove_userDoesNotExist_throwsException() {
- assertThrows(NeneException.class, () -> mUsers.find(USER_ID).remove());
+ assertThrows(NeneException.class, () -> mTestApis.users().find(USER_ID).remove());
}
@Test
public void remove_userExists_removesUser() {
- UserReference user = mUsers.createUser().create();
+ UserReference user = mTestApis.users().createUser().create();
user.remove();
- assertThat(mUsers.all().stream().anyMatch(u -> u.id() == user.id())).isFalse();
+ assertThat(mTestApis.users().all()).doesNotContain(user);
}
@Test
public void start_userDoesNotExist_throwsException() {
- assertThrows(NeneException.class, () -> mUsers.find(NON_EXISTING_USER_ID).start());
+ assertThrows(NeneException.class,
+ () -> mTestApis.users().find(NON_EXISTING_USER_ID).start());
}
@Test
public void start_userNotStarted_userIsStarted() {
- UserReference user = mUsers.createUser().create().stop();
+ UserReference user = mTestApis.users().createUser().create().stop();
user.start();
@@ -125,7 +125,7 @@
@Test
public void start_userAlreadyStarted_doesNothing() {
- UserReference user = mUsers.createUser().createAndStart();
+ UserReference user = mTestApis.users().createUser().createAndStart();
user.start();
@@ -138,12 +138,13 @@
@Test
public void stop_userDoesNotExist_throwsException() {
- assertThrows(NeneException.class, () -> mUsers.find(NON_EXISTING_USER_ID).stop());
+ assertThrows(NeneException.class,
+ () -> mTestApis.users().find(NON_EXISTING_USER_ID).stop());
}
@Test
public void stop_userStarted_userIsStopped() {
- UserReference user = mUsers.createUser().createAndStart();
+ UserReference user = mTestApis.users().createUser().createAndStart();
user.stop();
@@ -156,7 +157,7 @@
@Test
public void stop_userNotStarted_doesNothing() {
- UserReference user = mUsers.createUser().create().stop();
+ UserReference user = mTestApis.users().createUser().create().stop();
user.stop();
@@ -168,12 +169,12 @@
}
@Test
- public void switchTo_userIsSwitched() throws Exception {
+ public void switchTo_userIsSwitched() {
assumeTrue(
"Adopting Shell Permissions only works for Q+", SDK_INT >= Build.VERSION_CODES.Q);
// TODO(scottjonathan): In this case we can probably grant the permission through adb?
- UserReference user = mUsers.createUser().createAndStart();
+ UserReference user = mTestApis.users().createUser().createAndStart();
try {
SystemUtil.runWithShellPermissionIdentity(() -> {
// for INTERACT_ACROSS_USERS
@@ -194,8 +195,26 @@
assertThat(logs.poll()).isNotNull();
});
} finally {
- mUsers.system().switchTo();
+ mTestApis.users().system().switchTo();
user.remove();
}
}
+
+ @Test
+ public void stop_isWorkProfileOfCurrentUser_stops() {
+ UserType managedProfileType =
+ mTestApis.users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME);
+ UserReference profileUser = mTestApis.users().createUser()
+ .type(managedProfileType)
+ .parent(mTestApis.users().instrumented())
+ .createAndStart();
+
+ try {
+ profileUser.stop();
+
+ assertThat(profileUser.resolve().state()).isEqualTo(User.UserState.NOT_RUNNING);
+ } finally {
+ profileUser.remove();
+ }
+ }
}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java
index b87965c..0d8a1b3 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java
@@ -20,6 +20,8 @@
import static org.testng.Assert.assertThrows;
+import com.android.bedstead.nene.TestApis;
+
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -34,13 +36,13 @@
private static final UserType USER_TYPE = new UserType(new UserType.MutableUserType());
private static final String USER_NAME = "userName";
- private final Users mUsers = new Users();
+ private final TestApis mTestApis = new TestApis();
@Test
public void id_returnsId() {
User.MutableUser mutableUser = createValidMutableUser();
mutableUser.mId = USER_ID;
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.id()).isEqualTo(USER_ID);
}
@@ -50,14 +52,14 @@
User.MutableUser mutableUser = createValidMutableUser();
mutableUser.mId = null;
- assertThrows(NullPointerException.class, () -> new User(mUsers, mutableUser));
+ assertThrows(NullPointerException.class, () -> new User(mTestApis.users(), mutableUser));
}
@Test
public void serialNo_returnsSerialNo() {
User.MutableUser mutableUser = createValidMutableUser();
mutableUser.mSerialNo = SERIAL_NO;
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.serialNo()).isEqualTo(SERIAL_NO);
}
@@ -65,7 +67,7 @@
@Test
public void serialNo_notSet_returnsNull() {
User.MutableUser mutableUser = createValidMutableUser();
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.serialNo()).isNull();
}
@@ -74,7 +76,7 @@
public void name_returnsName() {
User.MutableUser mutableUser = createValidMutableUser();
mutableUser.mName = USER_NAME;
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.name()).isEqualTo(USER_NAME);
}
@@ -82,7 +84,7 @@
@Test
public void name_notSet_returnsNull() {
User.MutableUser mutableUser = createValidMutableUser();
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.name()).isNull();
}
@@ -91,7 +93,7 @@
public void type_returnsName() {
User.MutableUser mutableUser = createValidMutableUser();
mutableUser.mType = USER_TYPE;
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.type()).isEqualTo(USER_TYPE);
}
@@ -99,7 +101,7 @@
@Test
public void type_notSet_returnsNull() {
User.MutableUser mutableUser = createValidMutableUser();
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.type()).isNull();
}
@@ -108,7 +110,7 @@
public void hasProfileOwner_returnsHasProfileOwner() {
User.MutableUser mutableUser = createValidMutableUser();
mutableUser.mHasProfileOwner = true;
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.hasProfileOwner()).isTrue();
}
@@ -116,7 +118,7 @@
@Test
public void hasProfileOwner_notSet_returnsNull() {
User.MutableUser mutableUser = createValidMutableUser();
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.hasProfileOwner()).isNull();
}
@@ -125,7 +127,7 @@
public void isPrimary_returnsIsPrimary() {
User.MutableUser mutableUser = createValidMutableUser();
mutableUser.mIsPrimary = true;
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.isPrimary()).isTrue();
}
@@ -133,14 +135,14 @@
@Test
public void isPrimary_notSet_returnsNull() {
User.MutableUser mutableUser = createValidMutableUser();
- User user = new User(mUsers, mutableUser);
+ User user = new User(mTestApis.users(), mutableUser);
assertThat(user.isPrimary()).isNull();
}
@Test
public void state_userNotStarted_returnsState() {
- UserReference user = createUser();
+ UserReference user = mTestApis.users().createUser().create();
user.stop();
try {
@@ -153,8 +155,7 @@
@Test
@Ignore("TODO: Ensure we can enter the user locked state")
public void state_userLocked_returnsState() {
- UserReference user = createUser();
- user.start();
+ UserReference user = mTestApis.users().createUser().createAndStart();
try {
assertThat(user.resolve().state()).isEqualTo(User.UserState.RUNNING_LOCKED);
@@ -165,8 +166,7 @@
@Test
public void state_userUnlocked_returnsState() {
- UserReference user = createUser();
- user.start();
+ UserReference user = mTestApis.users().createUser().createAndStart();
try {
assertThat(user.resolve().state()).isEqualTo(User.UserState.RUNNING_UNLOCKED);
@@ -175,13 +175,19 @@
}
}
+ @Test
+ public void parent_returnsParent() {
+ UserReference parentUser = new User(mTestApis.users(), createValidMutableUser());
+ User.MutableUser mutableUser = createValidMutableUser();
+ mutableUser.mParent = parentUser;
+ User user = new User(mTestApis.users(), mutableUser);
+
+ assertThat(user.parent()).isEqualTo(parentUser);
+ }
+
private User.MutableUser createValidMutableUser() {
User.MutableUser mutableUser = new User.MutableUser();
mutableUser.mId = 1;
return mutableUser;
}
-
- private UserReference createUser() {
- return mUsers.createUser().name(USER_NAME).create();
- }
}
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 a47095f..f5e2dbd 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
@@ -18,15 +18,20 @@
import static android.os.Build.VERSION.SDK_INT;
+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;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
import android.os.Build;
import android.os.UserHandle;
-import com.android.bedstead.nene.exceptions.AdbException;
-import com.android.bedstead.nene.utils.ShellCommandUtils;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,31 +40,25 @@
@RunWith(JUnit4.class)
public class UsersTest {
- private static final String SYSTEM_USER_TYPE = "android.os.usertype.full.SYSTEM";
private static final int MAX_SYSTEM_USERS = UserType.UNLIMITED;
private static final int MAX_SYSTEM_USERS_PER_PARENT = UserType.UNLIMITED;
- private static final String MANAGED_PROFILE_TYPE = "android.os.usertype.profile.MANAGED";
- private static final String RESTRICTED_USER_TYPE = "android.os.usertype.full.RESTRICTED";
+ private static final String INVALID_TYPE_NAME = "invalidTypeName";
private static final int MAX_MANAGED_PROFILES = UserType.UNLIMITED;
private static final int MAX_MANAGED_PROFILES_PER_PARENT = 1;
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 final Users mUsers = new Users();
+ private final TestApis mTestApis = new TestApis();
// We don't want to test the exact list of any specific device, so we check that it returns
// some known types which will exist on the emulators (used for presubmit tests).
@Test
public void supportedTypes_containsManagedProfile() {
- assumeTrue(
- "supportedTypes is only supported on Android 11+",
- SDK_INT >= Build.VERSION_CODES.R);
-
UserType managedProfileUserType =
- mUsers.supportedTypes().stream().filter(
- (ut) -> ut.name().equals(MANAGED_PROFILE_TYPE)).findFirst().get();
+ mTestApis.users().supportedTypes().stream().filter(
+ (ut) -> ut.name().equals(MANAGED_PROFILE_TYPE_NAME)).findFirst().get();
assertThat(managedProfileUserType.baseType()).containsExactly(UserType.BaseType.PROFILE);
assertThat(managedProfileUserType.enabled()).isTrue();
@@ -70,13 +69,9 @@
@Test
public void supportedTypes_containsSystemUser() {
- assumeTrue(
- "supportedTypes is only supported on Android 11+",
- SDK_INT >= Build.VERSION_CODES.R);
-
UserType systemUserType =
- mUsers.supportedTypes().stream().filter(
- (ut) -> ut.name().equals(SYSTEM_USER_TYPE)).findFirst().get();
+ mTestApis.users().supportedTypes().stream().filter(
+ (ut) -> ut.name().equals(SYSTEM_USER_TYPE_NAME)).findFirst().get();
assertThat(systemUserType.baseType()).containsExactly(
UserType.BaseType.SYSTEM, UserType.BaseType.FULL);
@@ -86,19 +81,9 @@
}
@Test
- public void supportedTypes_androidVersionLessThan11_returnsNull() {
- assumeTrue("supportedTypes is supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
-
- assertThat(mUsers.supportedTypes()).isNull();
- }
-
- @Test
public void supportedType_validType_returnsType() {
- assumeTrue(
- "supportedTypes is only supported on Android 11+",
- SDK_INT >= Build.VERSION_CODES.R);
-
- UserType managedProfileUserType = mUsers.supportedType(MANAGED_PROFILE_TYPE);
+ UserType managedProfileUserType =
+ mTestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME);
assertThat(managedProfileUserType.baseType()).containsExactly(UserType.BaseType.PROFILE);
assertThat(managedProfileUserType.enabled()).isTrue();
@@ -108,101 +93,92 @@
}
@Test
- public void supportedType_invalidType_androidVersionLessThan11_returnsNull() {
- assumeTrue("supportedTypes is supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
-
- assertThat(mUsers.supportedType(MANAGED_PROFILE_TYPE)).isNull();
+ public void supportedType_invalidType_returnsNull() {
+ assertThat(mTestApis.users().supportedType(INVALID_TYPE_NAME)).isNull();
}
@Test
- public void supportedType_validType_androidVersionLessThan11_returnsNull() {
- assumeTrue("supportedTypes is supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
-
- assertThat(mUsers.supportedType(MANAGED_PROFILE_TYPE)).isNull();
- }
-
- @Test
- public void all_containsCreatedUser() throws Exception {
- int userId = createUser();
+ public void all_containsCreatedUser() {
+ UserReference user = mTestApis.users().createUser().create();
try {
- User foundUser = mUsers.all().stream().filter(
- u -> u.id() == userId).findFirst().get();
-
- assertThat(foundUser).isNotNull();
+ assertThat(mTestApis.users().all()).contains(user);
} finally {
- removeUser(userId);
+ user.remove();
}
}
@Test
- public void all_userAddedSinceLastCallToUsers_containsNewUser() throws Exception {
- int userId = createUser();
- mUsers.all();
- int userId2 = createUser();
+ public void all_userAddedSinceLastCallToUsers_containsNewUser() {
+ UserReference user = mTestApis.users().createUser().create();
+ mTestApis.users().all();
+ UserReference user2 = mTestApis.users().createUser().create();
try {
- User foundUser = mUsers.all().stream().filter(
- u -> u.id() == userId).findFirst().get();
-
- assertThat(foundUser).isNotNull();
+ assertThat(mTestApis.users().all()).contains(user2);
} finally {
- removeUser(userId);
- removeUser(userId2);
+ user.remove();
+ user2.remove();
}
}
@Test
- public void all_userRemovedSinceLastCallToUsers_doesNotContainRemovedUser() throws Exception {
- int userId = createUser();
- mUsers.all();
- removeUser(userId);
+ public void all_userRemovedSinceLastCallToUsers_doesNotContainRemovedUser() {
+ UserReference user = mTestApis.users().createUser().create();
+ mTestApis.users().all();
+ user.remove();
- assertThat(mUsers.all().stream().anyMatch(u -> u.id() == userId)).isFalse();
+ assertThat(mTestApis.users().all()).doesNotContain(user);
}
@Test
- public void find_userExists_returnsUserReference() throws Exception {
- int userId = createUser();
+ public void find_userExists_returnsUserReference() {
+ UserReference user = mTestApis.users().createUser().create();
try {
- assertThat(mUsers.find(userId)).isNotNull();
+ assertThat(mTestApis.users().find(user.id())).isEqualTo(user);
} finally {
- removeUser(userId);
+ user.remove();
}
}
@Test
public void find_userDoesNotExist_returnsUserReference() {
- assertThat(mUsers.find(NON_EXISTING_USER_ID)).isNotNull();
+ assertThat(mTestApis.users().find(NON_EXISTING_USER_ID)).isNotNull();
}
@Test
public void find_fromUserHandle_referencesCorrectId() {
- assertThat(mUsers.find(UserHandle.of(USER_ID)).id()).isEqualTo(USER_ID);
+ assertThat(mTestApis.users().find(UserHandle.of(USER_ID)).id()).isEqualTo(USER_ID);
}
@Test
public void find_constructedReferenceReferencesCorrectId() {
- assertThat(mUsers.find(USER_ID).id()).isEqualTo(USER_ID);
+ assertThat(mTestApis.users().find(USER_ID).id()).isEqualTo(USER_ID);
+ }
+
+ @Test
+ public void createUser_additionalSystemUser_throwsException() {
+ assertThrows(NeneException.class, () ->
+ mTestApis.users().createUser()
+ .type(mTestApis.users().supportedType(SYSTEM_USER_TYPE_NAME))
+ .create());
}
@Test
public void createUser_userIsCreated() {
- UserReference userReference = mUsers.createUser()
- .create();
+ UserReference user = mTestApis.users().createUser().create();
try {
- assertThat(
- mUsers.all().stream().anyMatch((u -> u.id() == userReference.id()))).isTrue();
+ assertThat(mTestApis.users().all()).contains(user);
} finally {
- userReference.remove();
+ user.remove();
}
}
@Test
public void createUser_createdUserHasCorrectName() {
- UserReference userReference = mUsers.createUser()
- .name(USER_NAME) // required
+ UserReference userReference = mTestApis.users().createUser()
+ .name(USER_NAME)
.create();
try {
@@ -214,10 +190,8 @@
@Test
public void createUser_createdUserHasCorrectTypeName() {
- assumeTrue("types are supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
-
- UserType type = mUsers.supportedType(RESTRICTED_USER_TYPE);
- UserReference userReference = mUsers.createUser()
+ UserType type = mTestApis.users().supportedType(SECONDARY_USER_TYPE_NAME);
+ UserReference userReference = mTestApis.users().createUser()
.type(type)
.create();
@@ -229,11 +203,102 @@
}
@Test
+ public void createUser_specifiesNullUserType_throwsException() {
+ UserBuilder userBuilder = mTestApis.users().createUser();
+
+ assertThrows(NullPointerException.class, () -> userBuilder.type(null));
+ }
+
+ @Test
+ public void createUser_specifiesSystemUserType_throwsException() {
+ UserType type = mTestApis.users().supportedType(SYSTEM_USER_TYPE_NAME);
+ UserBuilder userBuilder = mTestApis.users().createUser()
+ .type(type);
+
+ assertThrows(NeneException.class, userBuilder::create);
+ }
+
+ @Test
+ public void createUser_specifiesSecondaryUserType_createsUser() {
+ UserType type = mTestApis.users().supportedType(SECONDARY_USER_TYPE_NAME);
+ UserReference user = mTestApis.users().createUser().type(type).create();
+
+ try {
+ assertThat(user.resolve()).isNotNull();
+ } finally {
+ user.remove();
+ }
+ }
+
+ @Test
+ public void createUser_specifiesManagedProfileUserType_createsUser() {
+ UserReference systemUser = mTestApis.users().system();
+ UserType type = mTestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME);
+ UserReference user = mTestApis.users().createUser().type(type).parent(systemUser).create();
+
+ try {
+ assertThat(user.resolve()).isNotNull();
+ } finally {
+ user.remove();
+ }
+ }
+
+ @Test
+ public void createUser_createsProfile_parentIsSet() {
+ UserReference systemUser = mTestApis.users().system();
+ UserType type = mTestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME);
+ UserReference user = mTestApis.users().createUser().type(type).parent(systemUser).create();
+
+ try {
+ assertThat(user.resolve().parent()).isEqualTo(mTestApis.users().system());
+ } finally {
+ user.remove();
+ }
+ }
+
+ @Test
+ public void createUser_specifiesParentOnNonProfileType_throwsException() {
+ UserReference systemUser = mTestApis.users().system();
+ UserType type = mTestApis.users().supportedType(SECONDARY_USER_TYPE_NAME);
+ UserBuilder userBuilder = mTestApis.users().createUser().type(type).parent(systemUser);
+
+ assertThrows(NeneException.class, userBuilder::create);
+ }
+
+ @Test
+ public void createUser_specifiesProfileTypeWithoutParent_throwsException() {
+ UserType type = mTestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME);
+ UserBuilder userBuilder = mTestApis.users().createUser()
+ .type(type);
+
+ assertThrows(NeneException.class, userBuilder::create);
+ }
+
+ @Test
+ public void createUser_androidLessThanS_createsManagedProfileNotOnSystemUser_throwsException() {
+ assumeTrue("After Android S, managed profiles may be a profile of a non-system user",
+ SDK_INT < Build.VERSION_CODES.S);
+
+ UserReference nonSystemUser = mTestApis.users().createUser().create();
+
+ try {
+ UserType type = mTestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME);
+ UserBuilder userBuilder = mTestApis.users().createUser()
+ .type(type)
+ .parent(nonSystemUser);
+
+ assertThrows(NeneException.class, userBuilder::create);
+ } finally {
+ nonSystemUser.remove();
+ }
+ }
+
+ @Test
public void createAndStart_isStarted() {
User user = null;
try {
- user = mUsers.createUser().name(USER_NAME).createAndStart().resolve();
+ user = mTestApis.users().createUser().name(USER_NAME).createAndStart().resolve();
assertThat(user.state()).isEqualTo(User.UserState.RUNNING_UNLOCKED);
} finally {
if (user != null) {
@@ -244,28 +309,12 @@
@Test
public void system_hasId0() {
- assertThat(mUsers.system().id()).isEqualTo(0);
+ assertThat(mTestApis.users().system().id()).isEqualTo(0);
}
- private int createUser() {
- // We do ADB calls directly to ensure we are actually changing the system state and not just
- // internal nene state
- try {
- String createUserOutput = ShellCommandUtils.executeCommand("pm create-user testuser");
- return Integer.parseInt(createUserOutput.split("id ")[1].trim());
- } catch (AdbException e) {
- throw new AssertionError("Error creating user", e);
- }
- }
-
- private void removeUser(int userId) throws InterruptedException {
- // We do ADB calls directly to ensure we are actually changing the system state and not just
- // internal nene state
- try {
- ShellCommandUtils.executeCommand("pm remove-user " + userId);
- ShellCommandUtils.executeCommandUntilOutputValid("dumpsys user", (output) -> !output.contains("UserInfo{" + userId + ":"));
- } catch (AdbException e) {
- throw new AssertionError("Error removing user", e);
- }
+ @Test
+ public void instrumented_hasCurrentProccessId() {
+ assertThat(mTestApis.users().instrumented().id())
+ .isEqualTo(android.os.Process.myUserHandle().getIdentifier());
}
}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java
index a87c22d..226961e 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java
@@ -140,10 +140,11 @@
}
@Test
- public void executeAndValidateOutput_outputFilterMatched_returnsOutput() throws Exception {
+ public void execute_validate_outputFilterMatched_returnsOutput() throws Exception {
assertThat(
- ShellCommand.builder(LIST_USERS_COMMAND).
- executeAndValidateOutput(ALWAYS_PASS_OUTPUT_FILTER))
+ ShellCommand.builder(LIST_USERS_COMMAND)
+ .validate(ALWAYS_PASS_OUTPUT_FILTER)
+ .execute())
.isNotNull();
}
@@ -151,7 +152,8 @@
public void executeAndValidateOutput_outputFilterNotMatched_throwsException() {
assertThrows(AdbException.class,
() -> ShellCommand.builder(LIST_USERS_COMMAND)
- .executeAndValidateOutput(ALWAYS_FAIL_OUTPUT_FILTER));
+ .validate(ALWAYS_FAIL_OUTPUT_FILTER)
+ .execute());
}
@Test
@@ -178,4 +180,19 @@
.execute())
.contains(LIST_USERS_EXPECTED_OUTPUT);
}
+
+ @Test
+ public void executeAndParse_parseSucceeds_returnsCorrectValue() throws Exception {
+ assertThat((Integer) ShellCommand.builder(LIST_USERS_COMMAND)
+ .executeAndParseOutput((output) -> 3)).isEqualTo(3);
+ }
+
+ @Test
+ public void executeAndParse_parseFails_throwsException() {
+ assertThrows(AdbException.class, () ->
+ ShellCommand.builder(LIST_USERS_COMMAND)
+ .executeAndParseOutput((output) -> {
+ throw new IllegalStateException();
+ }));
+ }
}
diff --git a/common/device-side/bedstead/nene/testapps/TestApp1.xml b/common/device-side/bedstead/nene/testapps/TestApp1.xml
index 5772b0d..dcf8a60 100644
--- a/common/device-side/bedstead/nene/testapps/TestApp1.xml
+++ b/common/device-side/bedstead/nene/testapps/TestApp1.xml
@@ -21,5 +21,5 @@
<application
android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
</application>
- <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
</manifest>
diff --git a/common/device-side/bedstead/testapp/Android.bp b/common/device-side/bedstead/testapp/Android.bp
new file mode 100644
index 0000000..4c23a2d
--- /dev/null
+++ b/common/device-side/bedstead/testapp/Android.bp
@@ -0,0 +1,71 @@
+android_library {
+ name: "TestApp",
+ sdk_version: "test_current",
+ srcs: [
+ "src/main/java/**/*.java"
+ ],
+ static_libs: [
+ "Nene",
+ ],
+ manifest: "src/main/AndroidManifest.xml",
+ min_sdk_version: "27",
+ resource_zips: [":TestApp_Apps"],
+}
+
+android_test {
+ name: "TestAppTest",
+ srcs: [
+ "src/test/java/**/*.java"
+ ],
+ test_suites: [
+ "general-tests",
+ ],
+ static_libs: [
+ "Nene",
+ "TestApp",
+ "androidx.test.ext.junit",
+ "truth-prebuilt",
+ "testng" // for assertThrows
+ ],
+ manifest: "src/test/AndroidManifest.xml",
+ min_sdk_version: "27"
+}
+
+python_binary_host {
+ name: "index_testapps",
+ defaults: ["base_default"],
+ main: "tools/index/index_testapps.py",
+ srcs: [
+ "tools/index/index_testapps.py",
+ ]
+}
+
+java_genrule {
+ name: "TestApp_Apps",
+ srcs: [":EmptyTestApp", ":EmptyTestApp2"],
+ out: ["TestApp_Apps.res.zip"],
+ tools: ["soong_zip", "index_testapps"],
+ cmd: "mkdir -p $(genDir)/res/raw"
+ + " && cp $(location :EmptyTestApp) $(genDir)/res/raw"
+ + " && cp $(location :EmptyTestApp2) $(genDir)/res/raw"
+ + " && $(location index_testapps) --directory $(genDir)/res/raw"
+ + " && $(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res/raw"
+}
+
+android_test_helper_app {
+ name: "EmptyTestApp",
+ static_libs: [
+ "EventLib"
+ ],
+ manifest: "manifests/EmptyTestAppManifest.xml",
+ min_sdk_version: "27"
+}
+
+android_test_helper_app {
+ name: "EmptyTestApp2",
+ static_libs: [
+ "EventLib"
+ ],
+ manifest: "manifests/EmptyTestApp2Manifest.xml",
+ min_sdk_version: "27"
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/AndroidTest.xml b/common/device-side/bedstead/testapp/AndroidTest.xml
new file mode 100644
index 0000000..6ef4fe3
--- /dev/null
+++ b/common/device-side/bedstead/testapp/AndroidTest.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.
+ -->
+<configuration description="Config for Testapp test cases">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="TestAppTest.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.bedstead.testapp.test" />
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml b/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml
new file mode 100644
index 0000000..e96e221
--- /dev/null
+++ b/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.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.EmptyTestApp2">
+ <application
+ android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
+ </application>
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
+</manifest>
diff --git a/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml b/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml
new file mode 100644
index 0000000..1f07ddb
--- /dev/null
+++ b/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.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.EmptyTestApp">
+ <application
+ android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
+ </application>
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
+</manifest>
diff --git a/common/device-side/bedstead/testapp/src/main/AndroidManifest.xml b/common/device-side/bedstead/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..54be508
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/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.testapp">
+ <uses-sdk android:minSdkVersion="27" />
+ <application>
+ </application>
+</manifest>
diff --git a/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/NotFoundException.java b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/NotFoundException.java
new file mode 100644
index 0000000..669bb13
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/NotFoundException.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.bedstead.testapp;
+
+/** {@link Exception} thrown when a query doesn't match any test apps. */
+public class NotFoundException extends RuntimeException {
+ public NotFoundException(TestAppQueryBuilder query) {
+
+ }
+}
diff --git a/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestApp.java b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestApp.java
new file mode 100644
index 0000000..347df2c
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestApp.java
@@ -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 com.android.bedstead.testapp;
+
+import static com.android.compatibility.common.util.FileUtils.readInputStreamFully;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.packages.Package;
+import com.android.bedstead.nene.packages.PackageReference;
+import com.android.bedstead.nene.users.UserReference;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Represents a single test app which can be installed and interacted with. */
+public class TestApp {
+
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ private final TestApis mTestApis = new TestApis();
+ private final TestAppDetails mDetails;
+
+ TestApp(TestAppDetails details) {
+ if (details == null) {
+ throw new NullPointerException();
+ }
+ mDetails = details;
+ }
+
+ /**
+ * Get a {@link PackageReference} for the {@link TestApp}.
+ *
+ * <p>This will only be resolvable after the app is installed.
+ */
+ public PackageReference reference() {
+ return mTestApis.packages().find(packageName());
+ }
+
+ /**
+ * Get a {@link Package} for the {@link TestApp}, or {@code null} if it is not installed.
+ */
+ public Package resolve() {
+ return reference().resolve();
+ }
+
+ /**
+ * Install the {@link TestApp} on the device for the given {@link UserReference}.
+ */
+ public void install(UserReference user) {
+ mTestApis.packages().install(user, apkBytes());
+ }
+
+ /**
+ * Install the {@link TestApp} on the device for the given {@link UserHandle}.
+ */
+ public void install(UserHandle user) {
+ install(mTestApis.users().find(user));
+ }
+
+ private byte[] apkBytes() {
+ try (InputStream inputStream =
+ mContext.getResources().openRawResource(mDetails.mResourceIdentifier)) {
+ return readInputStreamFully(inputStream);
+ } catch (IOException e) {
+ throw new NeneException("Error when reading TestApp bytes", e);
+ }
+ }
+
+ /** Write the APK file to the given {@link File}. */
+ public void writeApkFile(File outputFile) throws IOException {
+ try (FileOutputStream output = new FileOutputStream(outputFile)) {
+ output.write(apkBytes());
+ }
+ }
+
+ /** The package name of the test app. */
+ public String packageName() {
+ return mDetails.mPackageName;
+ }
+}
diff --git a/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppDetails.java b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppDetails.java
new file mode 100644
index 0000000..51f7547
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppDetails.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.bedstead.testapp;
+
+class TestAppDetails {
+ String mPackageName;
+ int mResourceIdentifier;
+}
diff --git a/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppProvider.java b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppProvider.java
new file mode 100644
index 0000000..c4cab63
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppProvider.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.testapp;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Entry point to Test App. Used for querying for {@link TestApp} instances. */
+public final class TestAppProvider {
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ private boolean mTestAppsInitialised = false;
+ private final Set<TestAppDetails> mTestApps = new HashSet<>();
+
+ /** Begin a query for a {@link TestApp}. */
+ public TestAppQueryBuilder query() {
+ return new TestAppQueryBuilder(this);
+ }
+
+ /** Get any {@link TestApp}. */
+ public TestApp any() {
+ return query().get();
+ }
+
+ Set<TestAppDetails> testApps() {
+ initTestApps();
+ return mTestApps;
+ }
+
+ private void initTestApps() {
+ if (mTestAppsInitialised) {
+ return;
+ }
+ mTestAppsInitialised = true;
+
+ int indexId = sContext.getResources().getIdentifier(
+ "raw/index", /* defType= */ null, sContext.getPackageName());
+
+ try (InputStream inputStream = sContext.getResources().openRawResource(indexId);
+ BufferedReader bufferedReader =
+ new BufferedReader(new InputStreamReader(inputStream))) {
+ String apkName;
+ while ((apkName = bufferedReader.readLine()) != null) {
+ loadApk(apkName);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("TODO");
+ }
+ }
+
+ private void loadApk(String apkName) {
+ TestAppDetails details = new TestAppDetails();
+ details.mPackageName = "android." + apkName; // TODO: Actually index the package name
+ details.mResourceIdentifier = sContext.getResources().getIdentifier(
+ "raw/" + apkName, /* defType= */ null, sContext.getPackageName());
+
+ mTestApps.add(details);
+ }
+
+ void markTestAppUsed(TestAppDetails testApp) {
+ mTestApps.remove(testApp);
+ }
+}
diff --git a/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java
new file mode 100644
index 0000000..cd50513
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/** Builder for progressively building {@link TestApp} queries. */
+public final class TestAppQueryBuilder {
+ // TODO(scottjonathan): Consider how specific we can make this query builder - perhaps pull out
+ // the queries from EventLib and use them here too
+ private final TestAppProvider mProvider;
+ private String mPackageName = null;
+
+ TestAppQueryBuilder(TestAppProvider provider) {
+ if (provider == null) {
+ throw new NullPointerException();
+ }
+ mProvider = provider;
+ }
+
+ /**
+ * Query for a {@link TestApp} with a given package name.
+ *
+ * <p>Only use this filter when you are relying specifically on the package name itself. If you
+ * are relying on features you know the {@link TestApp} with that package name has, query for
+ * those features directly.
+ */
+ public TestAppQueryBuilder withPackageName(String packageName) {
+ if (packageName == null) {
+ throw new NullPointerException();
+ }
+ mPackageName = packageName;
+ return this;
+ }
+
+ /**
+ * Get the {@link TestApp} matching the query.
+ *
+ * @throws NotFoundException if there is no matching @{link TestApp}.
+ */
+ public TestApp get() {
+ // TODO(scottjonathan): Provide instructions on adding the TestApp if the query fails
+ return new TestApp(resolveQuery());
+ }
+
+ private TestAppDetails resolveQuery() {
+ for (TestAppDetails details : mProvider.testApps()) {
+ if (mPackageName != null && !mPackageName.equals(details.mPackageName)) {
+ continue;
+ }
+
+ mProvider.markTestAppUsed(details);
+ return details;
+ }
+
+ throw new NotFoundException(this);
+ }
+}
diff --git a/common/device-side/bedstead/testapp/src/test/AndroidManifest.xml b/common/device-side/bedstead/testapp/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..2e95934
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/test/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="com.android.bedstead.testapp.test">
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application android:label="TestApp Tests">
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.bedstead.testapp.test"
+ android:label="TestApp Tests" />
+</manifest>
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
new file mode 100644
index 0000000..5fdcffa
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppProviderTest.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 com.android.bedstead.testapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class TestAppProviderTest {
+
+ // Expects that this package name matches an actual test app
+ private static final String EXISTING_PACKAGENAME = "android.EmptyTestApp";
+
+ // Expects that this package name does not match an actual test app
+ private static final String NOT_EXISTING_PACKAGENAME = "not.existing.test.app";
+
+ private final TestAppProvider mTestAppProvider = new TestAppProvider();
+
+ @Test
+ public void get_queryMatches_returnsTestApp() {
+ TestAppQueryBuilder query = mTestAppProvider.query()
+ .withPackageName(EXISTING_PACKAGENAME);
+
+ assertThat(query.get()).isNotNull();
+ }
+
+ @Test
+ public void get_queryMatches_packageNameIsSet() {
+ TestAppQueryBuilder query = mTestAppProvider.query()
+ .withPackageName(EXISTING_PACKAGENAME);
+
+ assertThat(query.get().packageName()).isEqualTo(EXISTING_PACKAGENAME);
+ }
+
+ @Test
+ public void get_queryDoesNotMatch_throwsException() {
+ TestAppQueryBuilder query = mTestAppProvider.query()
+ .withPackageName(NOT_EXISTING_PACKAGENAME);
+
+ assertThrows(NotFoundException.class, query::get);
+ }
+
+ @Test
+ public void any_returnsTestApp() {
+ assertThat(mTestAppProvider.any()).isNotNull();
+ }
+
+ @Test
+ public void any_returnsDifferentTestApps() {
+ assertThat(mTestAppProvider.any()).isNotEqualTo(mTestAppProvider.any());
+ }
+
+ @Test
+ public void query_onlyReturnsTestAppOnce() {
+ mTestAppProvider.query().withPackageName(EXISTING_PACKAGENAME).get();
+
+ TestAppQueryBuilder query = mTestAppProvider.query().withPackageName(EXISTING_PACKAGENAME);
+
+ assertThrows(NotFoundException.class, query::get);
+ }
+
+ // TODO(scottjonathan): Once we support features other than package name, test that we can get
+ // different test apps by querying for the same thing
+}
diff --git a/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppTest.java b/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppTest.java
new file mode 100644
index 0000000..a325ef0
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/test/java/com/android/bedstead/testapp/TestAppTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.packages.Package;
+import com.android.bedstead.nene.users.UserReference;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+@RunWith(JUnit4.class)
+public class TestAppTest {
+
+ private final TestApis mTestApis = new TestApis();
+ private final UserReference mUser = mTestApis.users().instrumented();
+ private final UserHandle mUserHandle = mUser.userHandle();
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+
+ private final TestAppProvider mTestAppProvider = new TestAppProvider();
+
+ @Test
+ public void reference_returnsNeneReference() {
+ TestApp testApp = mTestAppProvider.any();
+
+ assertThat(testApp.reference()).isEqualTo(mTestApis.packages().find(testApp.packageName()));
+ }
+
+ @Test
+ public void resolve_returnsNenePackage() {
+ TestApp testApp = mTestAppProvider.any();
+ testApp.install(mUser);
+
+ try {
+ Package pkg = testApp.resolve();
+
+ assertThat(pkg.packageName()).isEqualTo(testApp.packageName());
+ } finally {
+ testApp.reference().uninstall(mUser);
+ }
+ }
+
+ @Test
+ public void install_userReference_installs() {
+ TestApp testApp = mTestAppProvider.any();
+
+ testApp.install(mUser);
+
+ try {
+ assertThat(testApp.resolve().installedOnUsers()).contains(mUser);
+ } finally {
+ testApp.reference().uninstall(mUser);
+ }
+ }
+
+ @Test
+ public void install_userHandle_installs() {
+ TestApp testApp = mTestAppProvider.any();
+
+ testApp.install(mUserHandle);
+
+ try {
+ assertThat(testApp.resolve().installedOnUsers()).contains(mUser);
+ } finally {
+ testApp.reference().uninstall(mUser);
+ }
+ }
+
+ @Test
+ public void writeApkFile_writesFile() throws Exception {
+ TestApp testApp = mTestAppProvider.any();
+ File filesDir = sContext.getExternalFilesDir(/* type= */ null);
+ File outputFile = new File(filesDir, "test.apk");
+ outputFile.delete();
+
+ testApp.writeApkFile(outputFile);
+
+ try {
+ assertThat(outputFile.exists()).isTrue();
+ } finally {
+ outputFile.delete();
+ }
+ }
+}
diff --git a/common/device-side/bedstead/testapp/tools/index/index_testapps.py b/common/device-side/bedstead/testapp/tools/index/index_testapps.py
new file mode 100644
index 0000000..26a7f1f
--- /dev/null
+++ b/common/device-side/bedstead/testapp/tools/index/index_testapps.py
@@ -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.
+
+import argparse
+from pathlib import Path
+
+def main():
+ args_parser = argparse.ArgumentParser(description='Generate index for test apps')
+ args_parser.add_argument('--directory', help='Directory containing test apps')
+ args = args_parser.parse_args()
+
+ pathlist = Path(args.directory).rglob('*.apk')
+ file_names = [p.name for p in pathlist]
+
+ # TODO(scottjonathan): Replace this with a proto with actual details
+ with open(args.directory + "/index.txt", "w") as outfile:
+ for file_name in file_names:
+ print(file_name.rsplit(".", 1)[0], file=outfile)
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/common/device-side/util-axt/Android.bp b/common/device-side/util-axt/Android.bp
index 44c810b..11da3d7 100644
--- a/common/device-side/util-axt/Android.bp
+++ b/common/device-side/util-axt/Android.bp
@@ -29,8 +29,7 @@
"ub-uiautomator",
"mockito-target-minus-junit4",
"androidx.annotation_annotation",
- "truth-prebuilt",
- "Harrier"
+ "truth-prebuilt"
],
libs: [
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 4a9bae8..2d0ac59 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
@@ -27,6 +27,7 @@
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
/**
* A receiver that allows caller to wait for the broadcast synchronously. Notice that you should not
@@ -50,21 +51,36 @@
private final BlockingQueue<Intent> mBlockingQueue;
private final List<String> mExpectedActions;
private final Context mContext;
+ @Nullable
+ private final Function<Intent, Boolean> mChecker;
public BlockingBroadcastReceiver(Context context, String expectedAction) {
- this(context, List.of(expectedAction));
+ this(context, expectedAction, /* checker= */ null);
+ }
+
+ public BlockingBroadcastReceiver(Context context, String expectedAction,
+ Function<Intent, Boolean> checker) {
+ this(context, List.of(expectedAction), checker);
}
public BlockingBroadcastReceiver(Context context, List<String> expectedActions) {
+ this(context, expectedActions, /* checker= */ null);
+ }
+
+ public BlockingBroadcastReceiver(
+ Context context, List<String> expectedActions, Function<Intent, Boolean> checker) {
mContext = context;
mExpectedActions = expectedActions;
mBlockingQueue = new ArrayBlockingQueue<>(1);
+ mChecker = checker;
}
@Override
public void onReceive(Context context, Intent intent) {
if (mExpectedActions.contains(intent.getAction())) {
- mBlockingQueue.add(intent);
+ if (mChecker == null || mChecker.apply(intent)) {
+ mBlockingQueue.add(intent);
+ }
}
}
@@ -75,6 +91,16 @@
}
/**
+ * Wait until the broadcast.
+ *
+ * <p>If no matching broadcasts is received within 60 seconds an {@link AssertionError} will
+ * be thrown.
+ */
+ public void awaitForBroadcastOrFail() {
+ awaitForBroadcastOrFail(DEFAULT_TIMEOUT_SECONDS * 1000);
+ }
+
+ /**
* 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.
*/
@@ -83,6 +109,18 @@
}
/**
+ * Wait until the broadcast.
+ *
+ * <p>If no matching broadcasts is received within the given timeout an {@link AssertionError}
+ * will be thrown.
+ */
+ public void awaitForBroadcastOrFail(long timeoutMillis) {
+ if (awaitForBroadcast(timeoutMillis) == null) {
+ throw new AssertionError("Did not receive matching broadcast");
+ }
+ }
+
+ /**
* 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.
*/
diff --git a/hostsidetests/abioverride/src/android/abioverride/cts/AbiOverrideTest.java b/hostsidetests/abioverride/src/android/abioverride/cts/AbiOverrideTest.java
index c2f14f0..167882e 100644
--- a/hostsidetests/abioverride/src/android/abioverride/cts/AbiOverrideTest.java
+++ b/hostsidetests/abioverride/src/android/abioverride/cts/AbiOverrideTest.java
@@ -85,6 +85,11 @@
*/
public void testAbiIs32bit() throws Exception {
ITestDevice device = getDevice();
+ //skip this test for 64bit only system
+ String prop32bit = device.getProperty("ro.product.cpu.abilist32");
+ if (prop32bit == null || prop32bit.trim().isEmpty()){
+ return;
+ }
// Clear logcat.
device.executeAdbCommand("logcat", "-c");
// Start the APK and wait for it to complete.
diff --git a/hostsidetests/adb/AndroidTest.xml b/hostsidetests/adb/AndroidTest.xml
index 3c429e7..b1cd198 100755
--- a/hostsidetests/adb/AndroidTest.xml
+++ b/hostsidetests/adb/AndroidTest.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration description="Config for the CTS adb host tests">
- <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController">
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController">
<option name="min-api-level" value="30" />
- <option name="api-level-prop" value="ro.product.first_api_level" />
</object>
<option name="test-suite-tag" value="cts" />
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
index d1507dff..45fa448 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
@@ -56,6 +56,7 @@
put(SPLIT_APK_DM, "split_feature_x.dm");
}};
+ private boolean mDmRequireFsVerity;
@Before
public void setUp() throws DeviceNotAvailableException {
@@ -63,6 +64,7 @@
String apkVerityMode = device.getProperty("ro.apk_verity.mode");
assumeTrue(device.getLaunchApiLevel() >= 30
|| APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
+ mDmRequireFsVerity = "true".equals(device.getProperty("pm.dexopt.dm.require_fsverity"));
}
@After
@@ -183,20 +185,22 @@
@CddTest(requirement="9.10/C-0-3,C-0-5")
@Test
- public void testInstallOnlyBaseHasFsvSig()
+ public void testInstallBaseWithFsvSigAndSplitWithout()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFile(BASE_APK)
.addFile(BASE_APK + FSV_SIG_SUFFIX)
.addFile(BASE_APK_DM)
+ .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
.addFile(SPLIT_APK)
.addFile(SPLIT_APK_DM)
+ .addFile(SPLIT_APK_DM + FSV_SIG_SUFFIX)
.runExpectingFailure();
}
@CddTest(requirement="9.10/C-0-3,C-0-5")
@Test
- public void testInstallOnlyDmHasFsvSig()
+ public void testInstallDmWithFsvSig()
throws DeviceNotAvailableException, FileNotFoundException {
new InstallMultiple()
.addFile(BASE_APK)
@@ -204,20 +208,46 @@
.addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
.addFile(SPLIT_APK)
.addFile(SPLIT_APK_DM)
- .runExpectingFailure();
+ .addFile(SPLIT_APK_DM + FSV_SIG_SUFFIX)
+ .run();
+ verifyFsverityInstall(BASE_APK_DM, SPLIT_APK_DM);
}
@CddTest(requirement="9.10/C-0-3,C-0-5")
@Test
- public void testInstallOnlySplitHasFsvSig()
+ public void testInstallDmWithMissingFsvSig()
throws DeviceNotAvailableException, FileNotFoundException {
- new InstallMultiple()
+ InstallMultiple installer = new InstallMultiple()
.addFile(BASE_APK)
.addFile(BASE_APK_DM)
+ .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
.addFile(SPLIT_APK)
- .addFile(SPLIT_APK + FSV_SIG_SUFFIX)
+ .addFile(SPLIT_APK_DM);
+ if (mDmRequireFsVerity) {
+ installer.runExpectingFailure();
+ } else {
+ installer.run();
+ verifyFsverityInstall(BASE_APK_DM);
+ }
+ }
+
+ @CddTest(requirement="9.10/C-0-3,C-0-5")
+ @Test
+ public void testInstallSplitWithFsvSigAndBaseWithout()
+ throws DeviceNotAvailableException, FileNotFoundException {
+ InstallMultiple installer = new InstallMultiple()
+ .addFile(BASE_APK)
+ .addFile(BASE_APK_DM)
+ .addFile(BASE_APK_DM + FSV_SIG_SUFFIX)
+ .addFile(SPLIT_APK)
.addFile(SPLIT_APK_DM)
- .runExpectingFailure();
+ .addFile(SPLIT_APK_DM + FSV_SIG_SUFFIX);
+ if (mDmRequireFsVerity) {
+ installer.runExpectingFailure();
+ } else {
+ installer.run();
+ verifyFsverityInstall(BASE_APK_DM, SPLIT_APK_DM);
+ }
}
@CddTest(requirement="9.10/C-0-3,C-0-5")
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
index f258dff..6852faf 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
@@ -25,6 +25,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.FileNotFoundException;
+
@RunWith(DeviceJUnit4ClassRunner.class)
public class IsolatedSplitsTests extends BaseAppSecurityTest {
private static final String PKG = "com.android.cts.isolatedsplitapp";
@@ -46,6 +48,27 @@
private static final String APK_FEATURE_C_pl = "CtsIsolatedSplitAppFeatureC_pl.apk";
private static final String APK_FEATURE_A_DiffRev = "CtsIsolatedSplitAppFeatureADiffRev.apk";
+ private static final String APK_BASE_WITHOUT_EXTRACTING = APK_BASE;
+ private static final String APK_FEATURE_JNI_WITHOUT_EXTRACTING =
+ "CtsIsolatedSplitAppExtractNativeLibsFalseJni.apk";
+ private static final String APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING =
+ "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProviderA.apk";
+ private static final String APK_FEATURE_PROVIDER_B_WITHOUT_EXTRACTING =
+ "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProviderB.apk";
+ private static final String APK_FEATURE_PROXY_WITHOUT_EXTRACTING =
+ "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProxy.apk";
+
+ private static final String APK_BASE_WITH_EXTRACTING =
+ "CtsIsolatedSplitAppExtractNativeLibsTrue.apk";
+ private static final String APK_FEATURE_JNI_WITH_EXTRACTING =
+ "CtsIsolatedSplitAppExtractNativeLibsTrueJni.apk";
+ private static final String APK_FEATURE_PROVIDER_A_WITH_EXTRACTING =
+ "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProviderA.apk";
+ private static final String APK_FEATURE_PROVIDER_B_WITH_EXTRACTING =
+ "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProviderB.apk";
+ private static final String APK_FEATURE_PROXY_WITH_EXTRACTING =
+ "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProxy.apk";
+
@Before
public void setUp() throws Exception {
Utils.prepareSingleUser(getDevice());
@@ -293,4 +316,153 @@
Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
"shouldNotFoundFeatureC");
}
+
+ private InstallMultiple configureInstallMultiple(boolean instant, String...apks)
+ throws FileNotFoundException {
+ InstallMultiple installMultiple = new InstallMultiple(instant);
+ for (String apk : apks) {
+ installMultiple.addFile(apk);
+ }
+ return installMultiple;
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testNativeInstallable_extractNativeLibs_baseFalse_splitTrue_full()
+ throws Exception {
+ configureInstallMultiple(false, APK_BASE_WITHOUT_EXTRACTING,
+ APK_FEATURE_JNI_WITH_EXTRACTING, APK_FEATURE_PROXY_WITH_EXTRACTING,
+ APK_FEATURE_PROVIDER_A_WITH_EXTRACTING).runExpectingFailure(
+ "INSTALL_FAILED_INVALID_APK");
+ }
+
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testNativeInstallable_extractNativeLibs_baseFalse_splitTrue_instant()
+ throws Exception {
+ configureInstallMultiple(true, APK_BASE_WITHOUT_EXTRACTING, APK_FEATURE_JNI_WITH_EXTRACTING,
+ APK_FEATURE_PROXY_WITH_EXTRACTING,
+ APK_FEATURE_PROVIDER_A_WITH_EXTRACTING).runExpectingFailure(
+ "INSTALL_FAILED_INVALID_APK");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testNativeInstallable_extractNativeLibs_baseFalse_splitFalse_full()
+ throws Exception {
+ configureInstallMultiple(false, APK_BASE_WITHOUT_EXTRACTING,
+ APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING,
+ APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING).run();
+ }
+
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testNativeInstallable_extractNativeLibs_baseFalse_splitFalse_instant()
+ throws Exception {
+ configureInstallMultiple(true, APK_BASE_WITHOUT_EXTRACTING,
+ APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING,
+ APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING).run();
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testNativeInstallable_extractNativeLibs_baseTrue_splitTrue_full()
+ throws Exception {
+ configureInstallMultiple(false, APK_BASE_WITH_EXTRACTING,
+ APK_FEATURE_JNI_WITH_EXTRACTING,
+ APK_FEATURE_PROXY_WITH_EXTRACTING, APK_FEATURE_PROVIDER_A_WITH_EXTRACTING).run();
+ }
+
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testNativeInstallable_extractNativeLibs_baseTrue_splitTrue_instant()
+ throws Exception {
+ configureInstallMultiple(true, APK_BASE_WITH_EXTRACTING, APK_FEATURE_JNI_WITH_EXTRACTING,
+ APK_FEATURE_PROXY_WITH_EXTRACTING, APK_FEATURE_PROVIDER_A_WITH_EXTRACTING).run();
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testNativeInstallable_extractNativeLibs_baseTrue_splitFalse_full()
+ throws Exception {
+ configureInstallMultiple(false, APK_BASE_WITH_EXTRACTING,
+ APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING,
+ APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING).run();
+ }
+
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testNativeInstallable_extractNativeLibs_baseTrue_splitFalse_instant()
+ throws Exception {
+ configureInstallMultiple(true, APK_BASE_WITH_EXTRACTING, APK_FEATURE_JNI_WITHOUT_EXTRACTING,
+ APK_FEATURE_PROXY_WITHOUT_EXTRACTING,
+ APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING).run();
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testAccessNativeSymbol_bothBaseAndSplitExtracting_full() throws Exception {
+ testAccessNativeSymbol(false, true, APK_BASE_WITH_EXTRACTING,
+ APK_FEATURE_JNI_WITH_EXTRACTING, APK_FEATURE_PROVIDER_A_WITH_EXTRACTING,
+ APK_FEATURE_PROVIDER_B_WITH_EXTRACTING, APK_FEATURE_PROXY_WITH_EXTRACTING);
+ }
+
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testAccessNativeSymbol_bothBaseAndSplitExtracting_instant() throws Exception {
+ testAccessNativeSymbol(true, true, APK_BASE_WITH_EXTRACTING,
+ APK_FEATURE_JNI_WITH_EXTRACTING, APK_FEATURE_PROVIDER_A_WITH_EXTRACTING,
+ APK_FEATURE_PROVIDER_B_WITH_EXTRACTING, APK_FEATURE_PROXY_WITH_EXTRACTING);
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testAccessNativeSymbol_onlyBaseExtracting_full() throws Exception {
+ testAccessNativeSymbol(false, true, APK_BASE_WITH_EXTRACTING,
+ APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING,
+ APK_FEATURE_PROVIDER_B_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING);
+ }
+
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testAccessNativeSymbol_onlyBaseExtracting_instant() throws Exception {
+ testAccessNativeSymbol(true, true, APK_BASE_WITH_EXTRACTING,
+ APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING,
+ APK_FEATURE_PROVIDER_B_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING);
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testAccessNativeSymbol_neitherBaseNorSplitExtracting_full() throws Exception {
+ testAccessNativeSymbol(false, false, APK_BASE_WITHOUT_EXTRACTING,
+ APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING,
+ APK_FEATURE_PROVIDER_B_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING);
+ }
+
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testAccessNativeSymbol_neitherBaseNorSplitExtracting_instant() throws Exception {
+ testAccessNativeSymbol(true, false, APK_BASE_WITHOUT_EXTRACTING,
+ APK_FEATURE_JNI_WITHOUT_EXTRACTING, APK_FEATURE_PROVIDER_A_WITHOUT_EXTRACTING,
+ APK_FEATURE_PROVIDER_B_WITHOUT_EXTRACTING, APK_FEATURE_PROXY_WITHOUT_EXTRACTING);
+ }
+
+ private void testAccessNativeSymbol(boolean instant, boolean expectedLoadedLibrary,
+ String baseApk, String jniApk, String providerAApk, String providerBApk,
+ String providerProxyApk) throws Exception {
+ configureInstallMultiple(instant, baseApk, jniApk, providerAApk, providerBApk,
+ providerProxyApk).run();
+ if (expectedLoadedLibrary) {
+ runDeviceTests(PKG, TEST_CLASS, "testNative_getNumberAViaProxy_shouldBeSeven");
+ runDeviceTests(PKG, TEST_CLASS, "testNative_getNumberBDirectly_shouldBeEleven");
+ runDeviceTests(PKG, TEST_CLASS, "testNative_getNumberADirectly_shouldBeSeven");
+ runDeviceTests(PKG, TEST_CLASS, "testNative_getNumberBViaProxy_shouldBeEleven");
+ } else {
+ runDeviceTests(PKG, TEST_CLASS, "testNative_cannotLoadSharedLibrary");
+ runDeviceTests(
+ PKG,
+ TEST_CLASS,
+ "testNativeSplit_withoutExtractLibs_nativeLibraryCannotBeLoaded");
+ }
+ }
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
index 628f8dd..0589ffc 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
@@ -23,7 +23,9 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import com.android.compatibility.common.util.ApiLevelUtil;
import com.android.compatibility.common.util.HostSideTestUtils;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
@@ -272,6 +274,145 @@
}
}
+ private boolean isAtLeastSDevice() throws Exception {
+ // The following tests targets API level >= S.
+ return ApiLevelUtil.isAfter(getDevice(), 30 /* BUILD.VERSION_CODES.R */)
+ || ApiLevelUtil.codenameEquals(getDevice(), "S");
+ }
+
+ @Test
+ public void resumeOnReboot_SingleUser_ServerBased_Success() throws Exception {
+ assumeTrue("Device isn't at least S", isAtLeastSDevice());
+
+ int[] users = Utils.prepareSingleUser(getDevice());
+ int initialUser = users[0];
+
+ deviceSetupServerBasedParameter();
+
+ try {
+ installTestPackages();
+
+ deviceSetup(initialUser);
+ deviceRequestLskf();
+ deviceLock(initialUser);
+ deviceEnterLskf(initialUser);
+ deviceRebootAndApply();
+
+ runDeviceTestsAsUser("testVerifyUnlockedAndDismiss", initialUser);
+ runDeviceTestsAsUser("testCheckServiceInteraction", initialUser);
+ } finally {
+ try {
+ // Remove secure lock screens and tear down test app
+ runDeviceTestsAsUser("testTearDown", initialUser);
+
+ deviceClearLskf();
+ } finally {
+ removeTestPackages();
+ deviceCleanupServerBasedParameter();
+
+ getDevice().rebootUntilOnline();
+ getDevice().waitForDeviceAvailable();
+ }
+ }
+ }
+
+ @Test
+ public void resumeOnReboot_SingleUser_MultiClient_ClientASuccess() throws Exception {
+ assumeTrue("Device isn't at least S", isAtLeastSDevice());
+
+ int[] users = Utils.prepareSingleUser(getDevice());
+ int initialUser = users[0];
+
+ deviceSetupServerBasedParameter();
+
+ final String clientA = "ClientA";
+ final String clientB = "ClientB";
+ try {
+ installTestPackages();
+
+ deviceSetup(initialUser);
+ deviceRequestLskf(clientA);
+ deviceRequestLskf(clientB);
+
+ deviceLock(initialUser);
+ deviceEnterLskf(initialUser);
+
+ // Client B's clear shouldn't affect client A's preparation.
+ deviceClearLskf(clientB);
+ deviceRebootAndApply(clientA);
+
+ runDeviceTestsAsUser("testVerifyUnlockedAndDismiss", initialUser);
+ runDeviceTestsAsUser("testCheckServiceInteraction", initialUser);
+ } finally {
+ try {
+ // Remove secure lock screens and tear down test app
+ runDeviceTestsAsUser("testTearDown", initialUser);
+
+ deviceClearLskf();
+ } finally {
+ removeTestPackages();
+ deviceCleanupServerBasedParameter();
+
+ getDevice().rebootUntilOnline();
+ getDevice().waitForDeviceAvailable();
+ }
+ }
+ }
+
+ @Test
+ public void resumeOnReboot_SingleUser_MultiClient_ClientBSuccess() throws Exception {
+ assumeTrue("Device isn't at least S", isAtLeastSDevice());
+
+ int[] users = Utils.prepareSingleUser(getDevice());
+ int initialUser = users[0];
+
+ deviceSetupServerBasedParameter();
+
+ final String clientA = "ClientA";
+ final String clientB = "ClientB";
+ try {
+ installTestPackages();
+
+ deviceSetup(initialUser);
+ deviceRequestLskf(clientA);
+
+ deviceLock(initialUser);
+ deviceEnterLskf(initialUser);
+
+ // Both clients have prepared
+ deviceRequestLskf(clientB);
+ deviceRebootAndApply(clientB);
+
+ runDeviceTestsAsUser("testVerifyUnlockedAndDismiss", initialUser);
+ runDeviceTestsAsUser("testCheckServiceInteraction", initialUser);
+ } finally {
+ try {
+ // Remove secure lock screens and tear down test app
+ runDeviceTestsAsUser("testTearDown", initialUser);
+
+ deviceClearLskf();
+ } finally {
+ removeTestPackages();
+ deviceCleanupServerBasedParameter();
+
+ getDevice().rebootUntilOnline();
+ getDevice().waitForDeviceAvailable();
+ }
+ }
+ }
+
+ private void deviceSetupServerBasedParameter() throws Exception {
+ getDevice().executeShellCommand("device_config put ota server_based_ror_enabled true");
+ getDevice().executeShellCommand(
+ "cmd lock_settings set-resume-on-reboot-provider-package " + PKG);
+ }
+
+ private void deviceCleanupServerBasedParameter() throws Exception {
+ getDevice().executeShellCommand("device_config put ota server_based_ror_enabled false");
+ getDevice().executeShellCommand(
+ "cmd lock_settings set-resume-on-reboot-provider-package ");
+ }
+
private void deviceSetup(int userId) throws Exception {
// To receive boot broadcasts, kick our other app out of stopped state
getDevice().executeShellCommand("am start -a android.intent.action.MAIN"
@@ -288,14 +429,22 @@
}
private void deviceRequestLskf() throws Exception {
- String res = getDevice().executeShellCommand("cmd recovery request-lskf " + PKG);
+ deviceRequestLskf(PKG);
+ }
+
+ private void deviceRequestLskf(String clientName) throws Exception {
+ String res = getDevice().executeShellCommand("cmd recovery request-lskf " + clientName);
if (res == null || !res.contains("success")) {
fail("could not set up recovery request-lskf");
}
}
private void deviceClearLskf() throws Exception {
- String res = getDevice().executeShellCommand("cmd recovery clear-lskf " + PKG);
+ deviceClearLskf(PKG);
+ }
+
+ private void deviceClearLskf(String clientName) throws Exception {
+ String res = getDevice().executeShellCommand("cmd recovery clear-lskf " + clientName);
if (res == null || !res.contains("success")) {
fail("could not clear-lskf");
}
@@ -326,7 +475,11 @@
}
private void deviceRebootAndApply() throws Exception {
- String res = getDevice().executeShellCommand("cmd recovery reboot-and-apply " + PKG
+ deviceRebootAndApply(PKG);
+ }
+
+ private void deviceRebootAndApply(String clientName) throws Exception {
+ String res = getDevice().executeShellCommand("cmd recovery reboot-and-apply " + clientName
+ " cts-test");
if (res != null && res.contains("Reboot and apply status: failure")) {
fail("could not call reboot-and-apply");
@@ -414,7 +567,8 @@
}
private boolean isSupportedDevice() throws Exception {
- return getDevice().hasFeature(FEATURE_DEVICE_ADMIN) && getDevice().hasFeature(FEATURE_REBOOT_ESCROW);
+ return getDevice().hasFeature(FEATURE_DEVICE_ADMIN)
+ && getDevice().hasFeature(FEATURE_REBOOT_ESCROW);
}
private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index 7552698..fe773e6 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -20,14 +20,21 @@
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.Abi;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.util.AbiUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.FileNotFoundException;
import java.util.HashMap;
+import java.util.Set;
/**
* Tests that verify installing of various split APKs from host side.
@@ -65,6 +72,10 @@
private static final String APK_mips64 = "CtsSplitApp_mips64.apk";
private static final String APK_mips = "CtsSplitApp_mips.apk";
+ private static final String APK_NUMBER_PROVIDER_A = "CtsSplitApp_number_provider_a.apk";
+ private static final String APK_NUMBER_PROVIDER_B = "CtsSplitApp_number_provider_b.apk";
+ private static final String APK_NUMBER_PROXY = "CtsSplitApp_number_proxy.apk";
+
private static final String APK_DIFF_REVISION = "CtsSplitAppDiffRevision.apk";
private static final String APK_DIFF_REVISION_v7 = "CtsSplitAppDiffRevision_v7.apk";
@@ -259,6 +270,54 @@
runDeviceTests(PKG, CLASS, "testNativeRevision_sub_shouldImplementBadly");
getInstallMultiple(instant, useNaturalAbi).inheritFrom(PKG).addFile(revisionApk).run();
runDeviceTests(PKG, CLASS, "testNativeRevision_sub_shouldImplementWell");
+
+ getInstallMultiple(instant, useNaturalAbi).inheritFrom(PKG)
+ .addFile(APK_NUMBER_PROVIDER_A)
+ .addFile(APK_NUMBER_PROVIDER_B)
+ .addFile(APK_NUMBER_PROXY).run();
+ runDeviceTests(PKG, CLASS, "testNative_getNumberADirectly_shouldBeSeven");
+ runDeviceTests(PKG, CLASS, "testNative_getNumberAViaProxy_shouldBeSeven");
+ runDeviceTests(PKG, CLASS, "testNative_getNumberBDirectly_shouldBeEleven");
+ runDeviceTests(PKG, CLASS, "testNative_getNumberBViaProxy_shouldBeEleven");
+ }
+
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ public void testNativeSplitForEachSupportedAbi_full() throws Exception {
+ testNativeForEachSupportedAbi(false);
+ }
+
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ public void testNativeSplitForEachSupportedAbi_instant() throws Exception {
+ testNativeForEachSupportedAbi(true);
+ }
+
+
+ private void specifyAbiToTest(boolean instant, String abiListProperty, String testMethodName)
+ throws FileNotFoundException, DeviceNotAvailableException {
+ final String propertyAbiListValue = getDevice().getProperty(abiListProperty);
+ final Set<String> supportedAbiSet =
+ AbiUtils.parseAbiListFromProperty(propertyAbiListValue);
+ for (String abi : supportedAbiSet) {
+ String apk = ABI_TO_APK.get(abi);
+ new InstallMultiple(instant, true).inheritFrom(PKG).addFile(apk).run();
+
+ // Without specifying abi for executing "adb shell am",
+ // a UnsatisfiedLinkError will happen.
+ IAbi iAbi = new Abi(abi, AbiUtils.getBitness(abi));
+ setAbi(iAbi);
+ runDeviceTests(PKG, CLASS, testMethodName);
+ }
+ }
+
+ private void testNativeForEachSupportedAbi(boolean instant)
+ throws DeviceNotAvailableException, FileNotFoundException {
+ new InstallMultiple(instant, true).addFile(APK).run();
+
+ // make sure this device can run both 32 bit and 64 bit
+ specifyAbiToTest(instant, "ro.product.cpu.abilist64", "testNative64Bit");
+ specifyAbiToTest(instant, "ro.product.cpu.abilist32", "testNative32Bit");
}
/**
@@ -306,8 +365,16 @@
for (String apk : ABI_TO_REVISION_APK.values()) {
instInheritFrom.addFile(apk);
}
+ instInheritFrom.addFile(APK_NUMBER_PROVIDER_A);
+ instInheritFrom.addFile(APK_NUMBER_PROVIDER_B);
+ instInheritFrom.addFile(APK_NUMBER_PROXY);
instInheritFrom.run();
runDeviceTests(PKG, CLASS, "testNativeRevision_sub_shouldImplementWell");
+
+ runDeviceTests(PKG, CLASS, "testNative_getNumberADirectly_shouldBeSeven");
+ runDeviceTests(PKG, CLASS, "testNative_getNumberAViaProxy_shouldBeSeven");
+ runDeviceTests(PKG, CLASS, "testNative_getNumberBDirectly_shouldBeEleven");
+ runDeviceTests(PKG, CLASS, "testNative_getNumberBViaProxy_shouldBeEleven");
}
/**
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml
index bf896db..3bbde47 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml
@@ -84,6 +84,18 @@
</intent-filter>
</provider>
+ <service
+ android:name=".RebootEscrowFakeService"
+ android:enabled = "true"
+ android:exported="true"
+ android:permission="android.permission.BIND_RESUME_ON_REBOOT_SERVICE"
+ android:directBootAware="true">
+ <intent-filter>
+ <action android:name="android.service.resumeonreboot.ResumeOnRebootService"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </service>
+
<uses-library android:name="android.test.runner" />
</application>
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
index 8e3a970..7a1f083 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
@@ -151,6 +151,18 @@
thisCount > lastCount);
}
+ public void testCheckServiceInteraction() {
+ boolean wrapCalled =
+ mDe.getSharedPreferences(RebootEscrowFakeService.SERVICE_PREFS, 0)
+ .getBoolean("WRAP_CALLED", false);
+ assertTrue(wrapCalled);
+
+ boolean unwrapCalled =
+ mDe.getSharedPreferences(RebootEscrowFakeService.SERVICE_PREFS, 0)
+ .getBoolean("UNWRAP_CALLED", false);
+ assertTrue(unwrapCalled);
+ }
+
public void testVerifyUnlockedAndDismiss() throws Exception {
doBootCountAfter();
assertUnlocked();
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/RebootEscrowFakeService.java b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/RebootEscrowFakeService.java
new file mode 100644
index 0000000..126337d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/RebootEscrowFakeService.java
@@ -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.
+ */
+
+package com.android.cts.encryptionapp;
+
+import android.content.Context;
+import android.util.Log;
+import android.service.resumeonreboot.ResumeOnRebootService;
+
+import javax.annotation.Nullable;
+
+/** A implementation for {@link IResumeOnRebootService}
+ *
+ * This class provides a fake implementation for the server based resume on reboot service.
+ * It's used for cts test to verify the functionality of platform code, and it won't talk to
+ * the server when wrap/unwrap is called.
+ */
+public class RebootEscrowFakeService extends ResumeOnRebootService {
+ static final String TAG = "RebootEscrowFakeService";
+
+ // Name of the shared preference for service interaction
+ static final String SERVICE_PREFS = "SERVICE_PREFERENCES";
+
+ @Nullable
+ @Override
+ public byte[] onWrap(byte[] blob, long lifeTimeInMillis) {
+ // Tests can this flag to verify that unwrap is called.
+ Context context =
+ getApplication().getApplicationContext().createDeviceProtectedStorageContext();
+ context.getSharedPreferences(SERVICE_PREFS, 0).edit()
+ .putBoolean("WRAP_CALLED", true).commit();
+
+ return blob;
+ }
+
+ @Nullable
+ @Override
+ public byte[] onUnwrap(byte[] wrappedBlob) {
+ // Tests can this flag to verify that unwrap is called.
+ Context context =
+ getApplication().getApplicationContext().createDeviceProtectedStorageContext();
+ context.getSharedPreferences(SERVICE_PREFS, 0).edit()
+ .putBoolean("UNWRAP_CALLED", true).commit();
+
+ return wrappedBlob;
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp
index f6d6d27..3f233b0 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp
@@ -15,6 +15,7 @@
android_test_helper_app {
name: "CtsExternalStorageApp",
defaults: ["cts_support_defaults"],
+ target_sdk_version: "29",
sdk_version: "30",
static_libs: [
"androidx.test.rules",
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
index 6472461..45ef6f9 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
@@ -14,13 +14,15 @@
// limitations under the License.
//
+TARGET_TEST_SUITES = [
+ "cts",
+ "general-tests",
+]
+
android_test_helper_app {
name: "CtsIsolatedSplitApp",
defaults: ["cts_support_defaults"],
- test_suites: [
- "cts",
- "general-tests",
- ],
+ test_suites: TARGET_TEST_SUITES,
sdk_version: "current",
// Feature splits are dependent on this base, so it must be exported.
export_package_resources: true,
@@ -34,4 +36,25 @@
srcs: ["src/**/*.java"],
// Generate a locale split.
package_splits: ["pl"],
+ use_embedded_native_libs: true, // default is true, android:extractNativeLibs="false"
+}
+
+android_test_helper_app {
+ name: "CtsIsolatedSplitAppExtractNativeLibsTrue",
+ defaults: ["cts_support_defaults"],
+ test_suites: TARGET_TEST_SUITES,
+ sdk_version: "current",
+ // Feature splits are dependent on this base, so it must be exported.
+ export_package_resources: true,
+ // Make sure our test locale polish is not stripped.
+ aapt_include_all_resources: true,
+ static_libs: [
+ "ctstestrunner-axt",
+ "androidx.test.rules",
+ "truth-prebuilt",
+ ],
+ srcs: ["src/**/*.java"],
+ // Generate a locale split.
+ package_splits: ["pl"],
+ use_embedded_native_libs: false, // android:extractNativeLibs="true"
}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/Android.bp
new file mode 100644
index 0000000..536c9e5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/Android.bp
@@ -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.
+//
+
+cc_test_library {
+ name: "libsplitappjni_isolated",
+ defaults: ["split_native_defaults"],
+ header_libs: ["jni_headers"],
+ shared_libs: ["liblog"],
+ srcs: ["jni/com_android_cts_isolatedsplitapp_Native.cpp"],
+}
+
+java_defaults {
+ name: "CtsSplitTestHelperApp_isolated_defaults",
+ compile_multilib: "both",
+
+ // TODO(b/179744452): Please add the following properties in individual modules because these
+ // properties can't inherit from java_defaults.
+ use_embedded_native_libs: false, // android:extractNativeLibs="true"
+ test_suites: TARGET_TEST_SUITES,
+}
+
+/**
+ * Isolated feature split with extracting native library
+ */
+android_test_helper_app {
+ name: "CtsIsolatedSplitAppExtractNativeLibsTrueJni",
+ defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+ manifest: "AndroidManifest_isolated_jni.xml",
+ jni_libs: ["libsplitappjni_isolated"],
+ use_embedded_native_libs: false, // android:extractNativeLibs="true"
+ srcs: ["src/**/*.java"],
+ test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+ name: "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProviderA",
+ defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+ manifest: "AndroidManifest_isolated_number_provider_a.xml",
+ jni_libs: ["libsplitapp_number_provider_a"],
+ use_embedded_native_libs: false, // android:extractNativeLibs="true"
+ test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+ name: "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProviderB",
+ defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+ manifest: "AndroidManifest_isolated_number_provider_b.xml",
+ jni_libs: ["libsplitapp_number_provider_b"],
+ use_embedded_native_libs: false, // android:extractNativeLibs="true"
+ test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+ name: "CtsIsolatedSplitAppExtractNativeLibsTrueNumberProxy",
+ defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+ manifest: "AndroidManifest_isolated_number_proxy.xml",
+ jni_libs: ["libsplitapp_number_proxy"],
+ use_embedded_native_libs: false, // android:extractNativeLibs="true"
+ test_suites: TARGET_TEST_SUITES,
+}
+
+/**
+ * Isolated feature split without extracting native library
+ */
+android_test_helper_app {
+ name: "CtsIsolatedSplitAppExtractNativeLibsFalseJni",
+ defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+ manifest: "AndroidManifest_isolated_jni.xml",
+ jni_libs: ["libsplitappjni_isolated"],
+ use_embedded_native_libs: true, // android:extractNativeLibs="false"
+ srcs: ["src/**/*.java"],
+ test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+ name: "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProviderA",
+ defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+ manifest: "AndroidManifest_isolated_number_provider_a.xml",
+ jni_libs: ["libsplitapp_number_provider_a"],
+ use_embedded_native_libs: true, // android:extractNativeLibs="false"
+ test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+ name: "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProviderB",
+ defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+ manifest: "AndroidManifest_isolated_number_provider_b.xml",
+ jni_libs: ["libsplitapp_number_provider_b"],
+ use_embedded_native_libs: true, // android:extractNativeLibs="false"
+ test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+ name: "CtsIsolatedSplitAppExtractNativeLibsFalseNumberProxy",
+ defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+ manifest: "AndroidManifest_isolated_number_proxy.xml",
+ jni_libs: ["libsplitapp_number_proxy"],
+ use_embedded_native_libs: true, // android:extractNativeLibs="false"
+ test_suites: TARGET_TEST_SUITES,
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_jni.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_jni.xml
new file mode 100644
index 0000000..74a53e9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_jni.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.cts.isolatedsplitapp"
+ featureSplit="isolated_native_jni_split">
+ <uses-split android:name="isolated_native_number_proxy_split"/>
+ <application>
+ <activity android:name="com.android.cts.isolatedsplitapp.jni.JniActivity">
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_a.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_a.xml
new file mode 100644
index 0000000..b17cb00
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_a.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="com.android.cts.isolatedsplitapp"
+ featureSplit="isolated_native_number_provider_a_split">
+ <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_b.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_b.xml
new file mode 100644
index 0000000..59d230f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_provider_b.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="com.android.cts.isolatedsplitapp"
+ featureSplit="isolated_native_number_provider_b_split">
+ <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_proxy.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_proxy.xml
new file mode 100644
index 0000000..92ef511
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_number_proxy.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="com.android.cts.isolatedsplitapp"
+ featureSplit="isolated_native_number_proxy_split">
+ <uses-split android:name="isolated_native_number_provider_a_split"/>
+ <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/jni/com_android_cts_isolatedsplitapp_Native.cpp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/jni/com_android_cts_isolatedsplitapp_Native.cpp
new file mode 100644
index 0000000..0542944
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/jni/com_android_cts_isolatedsplitapp_Native.cpp
@@ -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.
+ */
+
+#define LOG_TAG "IsolatedSplitApp"
+
+#include <android/log.h>
+#include <dlfcn.h>
+#include <stdio.h>
+
+#include "jni.h"
+
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+
+static jint add(JNIEnv* env, jobject thiz, jint numA, jint numB) {
+ return numA + numB;
+}
+
+typedef int (*pFuncGetNumber)();
+
+static jint get_number_from_other_library(const char* library_file_name,
+ const char* function_name) {
+ void* handle;
+ char* error;
+ handle = dlopen(library_file_name, RTLD_LAZY);
+ if (!handle) {
+ LOGE("Can't load %s: %s\n", library_file_name, dlerror());
+ return -1;
+ }
+ pFuncGetNumber functionGetNumber = (pFuncGetNumber)dlsym(handle, function_name);
+ if ((error = dlerror()) != NULL) {
+ LOGE("Can't load function %s: %s\n", function_name, error);
+ dlclose(handle);
+ return -2;
+ }
+ int ret = functionGetNumber();
+ dlclose(handle);
+
+ return ret;
+}
+
+static jint get_number_a_via_proxy(JNIEnv* env, jobject thiz) {
+ return get_number_from_other_library("libsplitapp_number_proxy.so", "get_number_a");
+}
+
+static jint get_number_b_via_proxy(JNIEnv* env, jobject thiz) {
+ return get_number_from_other_library("libsplitapp_number_proxy.so", "get_number_b");
+}
+
+static jint get_number_a_from_provider(JNIEnv* env, jobject thiz) {
+ return get_number_from_other_library("libsplitapp_number_provider_a.so", "get_number");
+}
+
+static jint get_number_b_from_provider(JNIEnv* env, jobject thiz) {
+ return get_number_from_other_library("libsplitapp_number_provider_b.so", "get_number");
+}
+
+static const char* classPathName = "com/android/cts/isolatedsplitapp/Native";
+
+static JNINativeMethod methods[] = {
+ {"add", "(II)I", reinterpret_cast<void*>(add)},
+ {"getNumberAViaProxy", "()I", reinterpret_cast<void**>(get_number_a_via_proxy)},
+ {"getNumberBViaProxy", "()I", reinterpret_cast<void**>(get_number_b_via_proxy)},
+ {"getNumberADirectly", "()I", reinterpret_cast<void**>(get_number_a_from_provider)},
+ {"getNumberBDirectly", "()I", reinterpret_cast<void**>(get_number_b_from_provider)},
+};
+
+static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods,
+ int numMethods) {
+ jclass clazz;
+
+ clazz = env->FindClass(className);
+ if (clazz == NULL) {
+ LOGE("Native registration unable to find class '%s'", className);
+ return JNI_FALSE;
+ }
+ if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
+ LOGE("RegisterNatives failed for '%s'", className);
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+static int registerNatives(JNIEnv* env) {
+ if (!registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]))) {
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ JNIEnv* env = NULL;
+
+ LOGI("JNI_OnLoad %s", classPathName);
+
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOGE("ERROR: GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (registerNatives(env) != JNI_TRUE) {
+ LOGE("ERROR: registerNatives failed");
+ return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_6;
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/src/com/android/cts/isolatedsplitapp/jni/JniActivity.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/src/com/android/cts/isolatedsplitapp/jni/JniActivity.java
new file mode 100644
index 0000000..342a7e8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/src/com/android/cts/isolatedsplitapp/jni/JniActivity.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.cts.isolatedsplitapp.jni;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class JniActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String errorMessage = "";
+ try {
+ System.loadLibrary("splitappjni_isolated");
+ } catch (UnsatisfiedLinkError e) {
+ errorMessage = e.getMessage();
+ }
+
+ final Intent resultIntent = new Intent();
+ resultIntent.putExtra(Intent.EXTRA_RETURN_RESULT, errorMessage);
+ setResult(1, resultIntent);
+ finish();
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/Native.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/Native.java
new file mode 100644
index 0000000..8f4c339
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/Native.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.cts.isolatedsplitapp;
+
+public class Native {
+ static boolean sIsLoadedLibrary;
+ static {
+ try {
+ System.loadLibrary("splitappjni_isolated");
+ sIsLoadedLibrary = true;
+ } catch (UnsatisfiedLinkError e) {
+ sIsLoadedLibrary = false;
+ }
+ }
+
+ public static native int add(int numberA, int numberB);
+ public static native int getNumberAViaProxy();
+ public static native int getNumberBViaProxy();
+ public static native int getNumberADirectly();
+ public static native int getNumberBDirectly();
+ public static boolean isLoadedLibrary() {
+ return sIsLoadedLibrary;
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
index abc6257..e77da9c 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.*;
import android.app.Activity;
+import android.app.Instrumentation;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -353,6 +354,48 @@
assertActivityDoNotExist(FEATURE_C_ACTIVITY);
}
+ @Test
+ public void testNativeJni_shouldBeLoaded() throws Exception {
+ assertThat(Native.add(7, 11), equalTo(18));
+ }
+
+ @Test
+ public void testNativeSplit_withoutExtractLibs_nativeLibraryCannotBeLoaded() throws Exception {
+ final Intent intent = new Intent();
+ intent.setClassName(PACKAGE, "com.android.cts.isolatedsplitapp.jni.JniActivity");
+ mActivityRule.launchActivity(intent);
+ mActivityRule.finishActivity();
+ Instrumentation.ActivityResult result = mActivityRule.getActivityResult();
+ final Intent resultData = result.getResultData();
+ final String errorMessage = resultData.getStringExtra(Intent.EXTRA_RETURN_RESULT);
+ assertThat(errorMessage, containsString("dlopen failed"));
+ }
+
+ @Test
+ public void testNative_getNumberADirectly_shouldBeSeven() throws Exception {
+ assertThat(Native.getNumberADirectly(), equalTo(7));
+ }
+
+ @Test
+ public void testNative_getNumberAViaProxy_shouldBeSeven() throws Exception {
+ assertThat(Native.getNumberAViaProxy(), equalTo(7));
+ }
+
+ @Test
+ public void testNative_getNumberBDirectly_shouldBeEleven() throws Exception {
+ assertThat(Native.getNumberBDirectly(), equalTo(11));
+ }
+
+ @Test
+ public void testNative_getNumberBViaProxy_shouldBeEleven() throws Exception {
+ assertThat(Native.getNumberBViaProxy(), equalTo(11));
+ }
+
+ @Test
+ public void testNative_cannotLoadSharedLibrary() throws Exception {
+ assertThat(Native.isLoadedLibrary(), equalTo(false));
+ }
+
private void assertActivityDoNotExist(ComponentName activity) {
try {
mActivityRule.launchActivity(new Intent().setComponent(activity));
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp
index 63a2841..c5565c3 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp
@@ -15,6 +15,7 @@
android_test_helper_app {
name: "CtsReadExternalStorageApp",
defaults: ["cts_support_defaults"],
+ target_sdk_version: "29",
sdk_version: "30",
static_libs: [
"androidx.test.rules",
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
index 2cf72bf..5f5dd91 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
@@ -25,6 +25,7 @@
static_libs: [
"androidx.test.rules",
"truth-prebuilt",
+ "hamcrest-library",
],
libs: [
"android.test.runner.stubs",
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh b/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
index 1a27d7a..2acd4cb 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
@@ -19,27 +19,40 @@
# use the NDK with maximum backward compatibility, such as the NDK bundle in Android SDK.
NDK_BUILD="$HOME/Android/android-ndk-r16b/ndk-build"
-function generateCopyRightComment {
- local year=$1
+function generateCopyRightComment() {
+ local year="$1"
+ local isAndroidManifest="$2"
+ local lineComment='#'
+ local copyrightStart=""
+ local copyrightEnd=""
+ local commentStart=""
+ local commentEnd=""
+ if [[ -n "$isAndroidManifest" ]]; then
+ lineComment=""
+ copyrightStart=$'<!--\n'
+ copyrightEnd=$'\n-->'
+ commentStart='<!--'
+ commentEnd='-->'
+ fi
copyrightInMk=$(
cat <<COPYRIGHT_COMMENT
-# Copyright (C) ${year} The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
+${copyrightStart}${lineComment} Copyright (C) ${year} The Android Open Source Project
+${lineComment}
+${lineComment} Licensed under the Apache License, Version 2.0 (the "License");
+${lineComment} you may not use this file except in compliance with the License.
+${lineComment} You may obtain a copy of the License at
+${lineComment}
+${lineComment} http://www.apache.org/licenses/LICENSE-2.0
+${lineComment}
+${lineComment} Unless required by applicable law or agreed to in writing, software
+${lineComment} distributed under the License is distributed on an "AS IS" BASIS,
+${lineComment} WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+${lineComment} See the License for the specific language governing permissions and
+${lineComment} limitations under the License.${copyrightEnd}
-# Automatically generated file from build_libs.sh.
-# DO NOT MODIFY THIS FILE.
+${commentStart}${lineComment} Automatically generated file from build_libs.sh.${commentEnd}
+${commentStart}${lineComment} DO NOT MODIFY THIS FILE.${commentEnd}
COPYRIGHT_COMMENT
)
@@ -59,8 +72,9 @@
}
function generateAndroidManifest {
- local targetFile=$1
- local arch=$2
+ local targetFile="$1"
+ local arch="$2"
+ local splitNamePart="$3"
(
cat <<ANDROIDMANIFEST
<?xml version="1.0" encoding="utf-8"?>
@@ -68,7 +82,7 @@
<!-- DO NOT MODIFY THIS FILE. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.splitapp"
- split="lib_${arch}">
+ split="lib${splitNamePart}_${arch}">
<application android:hasCode="false" />
</manifest>
ANDROIDMANIFEST
@@ -78,14 +92,9 @@
function generateModuleForContentPartialMk {
local arch="$1"
- local packagePartialName=""
- local rawDir="raw"
- local aaptRevisionFlags=""
- if [[ -n "$2" ]]; then
- packagePartialName="_revision${2}"
- rawDir="raw_revision"
- aaptRevisionFlags=" --revision-code ${2}"
- fi
+ local packagePartialName="$2"
+ local rawDir="$3"
+ local aaptRevisionFlags="$4"
localPackage=$(
cat <<MODULE_CONTENT_FOR_PARTIAL_MK
@@ -113,8 +122,10 @@
local targetFile="$1"
local arch="$2"
local copyrightInMk=$(generateCopyRightComment "2014")
- local baseSplitMkModule=$(generateModuleForContentPartialMk "${arch}")
- local revisionSplitMkModule=$(generateModuleForContentPartialMk "${arch}" 12)
+ local baseSplitMkModule=$(generateModuleForContentPartialMk "${arch}" "" "raw" "")
+ local revisionSplitMkModule=$(generateModuleForContentPartialMk "${arch}" "_revision12" \
+ "raw_revision" " --revision-code 12")
+
(
cat <<LIBS_ARCH_ANDROID_MK
#
@@ -143,7 +154,8 @@
mv tmp/$arch/raw/lib/$arch/libsplitappjni_revision.so \
tmp/$arch/raw_revision/lib/$arch/libsplitappjni.so
- generateAndroidManifest "tmp/$arch/AndroidManifest.xml" "${arch//[^a-zA-Z0-9_]/_}"
+ archWithoutDash="${arch//[^a-zA-Z0-9_]/_}"
+ generateAndroidManifest "tmp/$arch/AndroidManifest.xml" "${archWithoutDash}" ""
generateAndroidMk "tmp/$arch/Android.mk" "$arch"
)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.bp
new file mode 100644
index 0000000..8a60617
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.bp
@@ -0,0 +1,128 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_defaults {
+ name: "split_native_defaults",
+ gtest: false,
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ ],
+ target: {
+ android_arm: {
+ cflags: [
+ "-D__ANDROID_ARCH__=\"armeabi-v7a\"",
+ ],
+ },
+ android_arm64: {
+ cflags: [
+ "-D__ANDROID_ARCH__=\"arm64-v8a\"",
+ ],
+ },
+ android_x86: {
+ cflags: [
+ "-D__ANDROID_ARCH__=\"x86\"",
+ ],
+ },
+ android_x86_64: {
+ cflags: [
+ "-D__ANDROID_ARCH__=\"x86_64\"",
+ ],
+ },
+ },
+ sdk_version: "current",
+}
+
+cc_defaults {
+ name: "split_number_provider_defaults",
+ defaults: ["split_native_defaults"],
+ srcs: ["number_providers.cpp"],
+}
+
+cc_test_library {
+ name: "libsplitapp_number_provider_a",
+ defaults: ["split_number_provider_defaults"],
+ cflags: [
+ "-DANDROID_SPLIT_APP_NUMBER_PROVIDER_A_SO=1",
+ ],
+}
+
+cc_test_library {
+ name: "libsplitapp_number_provider_b",
+ defaults: ["split_number_provider_defaults"],
+ cflags: [
+ "-DANDROID_SPLIT_APP_NUMBER_PROVIDER_B_SO=1",
+ ],
+}
+
+cc_test_library {
+ name: "libsplitapp_number_proxy",
+ defaults: ["split_number_provider_defaults"],
+ cflags: [
+ "-DANDROID_SPLIT_APP_NUMBER_PROXY_SO=1",
+ ],
+}
+
+
+TARGET_TEST_SUITES = [
+ "cts",
+ "general-tests",
+]
+
+/**
+ * Non-isolated split feature
+ */
+java_defaults {
+ name: "CtsSplitTestHelperApp_defaults",
+ certificate: ":cts-testkey1",
+ aaptflags: [
+ "--replace-version",
+ "--version-code 100",
+ ],
+ test_suites: TARGET_TEST_SUITES,
+}
+
+java_defaults {
+ name: "CtsSplitTestHelperApp_number_provider_defaults",
+ defaults: ["CtsSplitTestHelperApp_defaults"],
+ compile_multilib: "both",
+ test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+ name: "CtsSplitApp_number_provider_a",
+ defaults: ["CtsSplitTestHelperApp_number_provider_defaults"],
+ manifest: "AndroidManifest_number_provider_a.xml",
+ jni_libs: ["libsplitapp_number_provider_a"],
+ test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+ name: "CtsSplitApp_number_provider_b",
+ defaults: ["CtsSplitTestHelperApp_number_provider_defaults"],
+ manifest: "AndroidManifest_number_provider_b.xml",
+ jni_libs: ["libsplitapp_number_provider_b"],
+ test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+ name: "CtsSplitApp_number_proxy",
+ defaults: ["CtsSplitTestHelperApp_number_provider_defaults"],
+ manifest: "AndroidManifest_number_proxy.xml",
+ jni_libs: ["libsplitapp_number_proxy"],
+ test_suites: TARGET_TEST_SUITES,
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
index 84e88dc..46894ce 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
@@ -18,6 +18,15 @@
# Please don't change this file to Android.bp.
LOCAL_PATH := $(call my-dir)
+# Default cflags
+MY_CFLAGS := -Wall -Werror -Wno-unused-parameter -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\"
+
+# If the TARGET_ARCH_ABI is 32bit, it adds __LIVE_ONLY_32BIT__ in MY_CFLAGS.
+ABIS_FOR_32BIT_ONLY := armeabi-v7a armeabi x86 mips
+ifneq ($(filter $(TARGET_ARCH_ABI),$(ABIS_FOR_32BIT_ONLY)),)
+MY_CFLAGS += -D__LIVE_ONLY_32BIT__=1
+endif
+
include $(CLEAR_VARS)
LOCAL_MODULE := libsplitappjni
@@ -25,7 +34,7 @@
LOCAL_LDLIBS += -llog
-LOCAL_CFLAGS := -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\"
+LOCAL_CFLAGS := $(MY_CFLAGS)
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts general-tests
@@ -39,8 +48,7 @@
LOCAL_LDLIBS += -llog
-LOCAL_CFLAGS := -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\" \
- -D__REVISION_HAVE_SUB__=1
+LOCAL_CFLAGS := $(MY_CFLAGS) -D__REVISION_HAVE_SUB__=1
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_a.xml b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_a.xml
new file mode 100644
index 0000000..1c6f2f1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_a.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.
+-->
+
+<!-- Automatically generated file from build_libs.sh.-->
+<!-- DO NOT MODIFY THIS FILE.-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.splitapp"
+ split="lib_number_provider_a">
+ <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_b.xml b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_b.xml
new file mode 100644
index 0000000..ee9baf5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_b.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.
+-->
+
+<!-- Automatically generated file from build_libs.sh.-->
+<!-- DO NOT MODIFY THIS FILE.-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.splitapp"
+ split="lib_number_provider_b">
+ <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_proxy.xml b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_proxy.xml
new file mode 100644
index 0000000..9d5c84e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_proxy.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.
+-->
+
+<!-- Automatically generated file from build_libs.sh.-->
+<!-- DO NOT MODIFY THIS FILE.-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.splitapp"
+ split="lib_number_proxy">
+ <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp b/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp
index eafb658..0329395 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp
@@ -18,12 +18,61 @@
#include <android/log.h>
#include <stdio.h>
+#include <dlfcn.h>
#include "jni.h"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
+typedef int (*pFuncGetNumber)();
+
+static jint get_number_from_other_library(
+ const char* library_file_name, const char* function_name) {
+ void *handle;
+ char *error;
+ handle = dlopen (library_file_name, RTLD_LAZY);
+ if (!handle) {
+ LOGE("Can't load %s: %s\n", library_file_name, dlerror());
+ return -1;
+ }
+ pFuncGetNumber functionGetNumber = (pFuncGetNumber) dlsym(handle, function_name);
+ if ((error = dlerror()) != NULL) {
+ LOGE("Can't load function %s: %s\n", function_name, error);
+ dlclose(handle);
+ return -2;
+ }
+ int ret = functionGetNumber();
+ dlclose(handle);
+
+ return ret;
+}
+
+static jint get_number_a_via_proxy(JNIEnv *env, jobject thiz) {
+ return get_number_from_other_library("libsplitapp_number_proxy.so", "get_number_a");
+}
+
+static jint get_number_b_via_proxy(JNIEnv *env, jobject thiz) {
+ return get_number_from_other_library("libsplitapp_number_proxy.so", "get_number_b");
+}
+
+static jint get_number_a_from_provider(JNIEnv *env, jobject thiz) {
+ return get_number_from_other_library("libsplitapp_number_provider_a.so", "get_number");
+}
+
+static jint get_number_b_from_provider(JNIEnv *env, jobject thiz) {
+ return get_number_from_other_library("libsplitapp_number_provider_b.so", "get_number");
+}
+
+#ifdef __LIVE_ONLY_32BIT__
+#define ABI_BITNESS 32
+#else // __LIVE_ONLY_32BIT__
+#define ABI_BITNESS 64
+#endif // __LIVE_ONLY_32BIT__
+
+static jint get_abi_bitness(JNIEnv* env, jobject thiz) {
+ return ABI_BITNESS;
+}
static jint add(JNIEnv *env, jobject thiz, jint a, jint b) {
int result = a + b;
@@ -49,9 +98,14 @@
static const char *classPathName = "com/android/cts/splitapp/Native";
static JNINativeMethod methods[] = {
+ {"getAbiBitness", "()I", (void*)get_abi_bitness},
{"add", "(II)I", (void*)add},
{"arch", "()Ljava/lang/String;", (void*)arch},
{"sub", "(II)I", (void*)sub},
+ {"getNumberAViaProxy", "()I", (void*) get_number_a_via_proxy},
+ {"getNumberBViaProxy", "()I", (void*) get_number_b_via_proxy},
+ {"getNumberADirectly", "()I", (void*) get_number_a_from_provider},
+ {"getNumberBDirectly", "()I", (void*) get_number_b_from_provider},
};
static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) {
@@ -90,9 +144,9 @@
JNIEnv* env = NULL;
#ifdef __REVISION_HAVE_SUB__
- LOGI("JNI_OnLoad revision");
+ LOGI("JNI_OnLoad revision %d bits", ABI_BITNESS);
#else // __REVISION_HAVE_SUB__
- LOGI("JNI_OnLoad");
+ LOGI("JNI_OnLoad %d bits", ABI_BITNESS);
#endif // __REVISION_HAVE_SUB__
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/number_providers.cpp b/hostsidetests/appsecurity/test-apps/SplitApp/jni/number_providers.cpp
new file mode 100644
index 0000000..ff19355
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/number_providers.cpp
@@ -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.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The content of libsplitapp_number_provider_proxy.so
+ */
+#ifdef ANDROID_SPLIT_APP_NUMBER_PROXY_SO
+#include <dlfcn.h>
+
+const char* kFunctionName = "get_number";
+
+typedef int (*pFuncGetNumber)();
+
+int get_number(const char* libraryFileName) {
+ void *handle;
+ char *error;
+ handle = dlopen (libraryFileName, RTLD_LAZY);
+ if (!handle) {
+ return -1;
+ }
+ pFuncGetNumber functionGetNumber = (pFuncGetNumber) dlsym(handle, kFunctionName);
+ if ((error = dlerror()) != NULL) {
+ dlclose(handle);
+ return -2;
+ }
+ int ret = functionGetNumber();
+ dlclose(handle);
+
+ return ret;
+}
+
+int get_number_a() {
+ return get_number("libsplitapp_number_provider_a.so");
+}
+
+int get_number_b() {
+ return get_number("libsplitapp_number_provider_b.so");
+}
+
+#endif // ANDROID_SPLIT_APP_NUMBER_PROXY_SO
+
+/**
+ * The content of libsplitapp_number_provider_a.so
+ */
+#ifdef ANDROID_SPLIT_APP_NUMBER_PROVIDER_A_SO
+int get_number() {
+ return 7;
+}
+#endif // ANDROID_SPLIT_APP_NUMBER_PROVIDER_A_SO
+
+
+/**
+ * The content of libsplitapp_number_provider_b.so
+ */
+#ifdef ANDROID_SPLIT_APP_NUMBER_PROVIDER_B_SO
+int get_number() {
+ return 11;
+}
+#endif // ANDROID_SPLIT_APP_NUMBER_PROVIDER_B_SO
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
index 97d7860..427a89e 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so
index a46d0e3..86f2b35 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
index ec5b2fe..ddf14e0 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so
index 501e48e..b5c0be7 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
index 152512b..1a979df 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so
index 33f9006..d4e34fa 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
index 21a2481..f50540d 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so
index c8fa555..78b6faf 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
index 8a9ad3b..bd298c4 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so
index d5b07dc..a1e67f2 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
index dc2beca..9325b09 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so
index 5c839aa..d7e2014 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
index 136960f..d977407 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so
index 6f36e71..3cc4b22 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java
index 4d10e15..8759068 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java
@@ -24,4 +24,9 @@
public static native int add(int a, int b);
public static native String arch();
public static native int sub(int a, int b);
+ public static native int getAbiBitness();
+ public static native int getNumberAViaProxy();
+ public static native int getNumberBViaProxy();
+ public static native int getNumberADirectly();
+ public static native int getNumberBDirectly();
}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
index 29d8d93..2308b98 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
@@ -48,6 +48,7 @@
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Build;
import android.os.ConditionVariable;
import android.os.Environment;
import android.system.Os;
@@ -78,6 +79,7 @@
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
@@ -280,6 +282,44 @@
}
@Test
+ public void testNative64Bit() throws Exception {
+ Log.d(TAG, "The device supports 32Bit ABIs \""
+ + Arrays.deepToString(Build.SUPPORTED_32_BIT_ABIS) + "\" and 64Bit ABIs \""
+ + Arrays.deepToString(Build.SUPPORTED_64_BIT_ABIS) + "\"");
+
+ assertThat(Native.getAbiBitness()).isEqualTo(64);
+ }
+
+ @Test
+ public void testNative32Bit() throws Exception {
+ Log.d(TAG, "The device supports 32Bit ABIs \""
+ + Arrays.deepToString(Build.SUPPORTED_32_BIT_ABIS) + "\" and 64Bit ABIs \""
+ + Arrays.deepToString(Build.SUPPORTED_64_BIT_ABIS) + "\"");
+
+ assertThat(Native.getAbiBitness()).isEqualTo(32);
+ }
+
+ @Test
+ public void testNative_getNumberADirectly_shouldBeSeven() throws Exception {
+ assertThat(Native.getNumberADirectly()).isEqualTo(7);
+ }
+
+ @Test
+ public void testNative_getNumberAViaProxy_shouldBeSeven() throws Exception {
+ assertThat(Native.getNumberAViaProxy()).isEqualTo(7);
+ }
+
+ @Test
+ public void testNative_getNumberBDirectly_shouldBeEleven() throws Exception {
+ assertThat(Native.getNumberBDirectly()).isEqualTo(11);
+ }
+
+ @Test
+ public void testNative_getNumberBViaProxy_shouldBeEleven() throws Exception {
+ assertThat(Native.getNumberBViaProxy()).isEqualTo(11);
+ }
+
+ @Test
public void testFeatureWarmBase() throws Exception {
final Resources r = getContext().getResources();
final PackageManager pm = getContext().getPackageManager();
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/Android.bp b/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
index ec7f238..0804cc5 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
+++ b/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
@@ -29,6 +29,9 @@
"ub-uiautomator",
"androidx.test.rules",
],
- libs: ["android.test.base"],
- sdk_version: "current",
+
+ libs: [
+ "android.test.base",
+ "android.test.runner",],
+ sdk_version: "test_current",
}
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java b/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java
index 69945a6..7ee9acb 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java
+++ b/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java
@@ -18,14 +18,9 @@
import android.accounts.Account;
import android.accounts.AccountManager;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
import android.content.Context;
-import android.os.Bundle;
import android.test.AndroidTestCase;
-
-import java.io.IOException;
+import android.util.Log;
/**
* Functionality tests for
@@ -37,8 +32,10 @@
*/
public class AccountUtilsTest extends AndroidTestCase {
+ private static final String TAG = AccountUtilsTest.class.getSimpleName();
+
// Account type for MockAccountAuthenticator
- private final static Account ACCOUNT = new Account("user0",
+ private static final Account ACCOUNT = new Account("testUser",
MockAccountAuthenticator.ACCOUNT_TYPE);
private AccountManager mAccountManager;
@@ -46,6 +43,7 @@
@Override
protected void setUp() throws Exception {
super.setUp();
+ Log.d(TAG, "setUp(): running on user " + mContext.getUserId());
mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
index 5bf6f20..bbd6732 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
@@ -19,7 +19,6 @@
import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
-import android.net.VpnService;
import android.os.Bundle;
import android.os.UserManager;
import android.util.Log;
@@ -49,6 +48,7 @@
private static final String RESTRICTION_DONT_ESTABLISH = "vpn.dont_establish";
private static final String CONNECTIVITY_CHECK_HOST = "connectivitycheck.gstatic.com";
private static final int VPN_ON_START_TIMEOUT_MS = 5_000;
+ private static final long CONNECTIVITY_WAIT_TIME_NS = 30_000_000_000L;
private String mPackageName;
@@ -112,7 +112,7 @@
// Tests that changes to lockdown allowlist are applied correctly.
public void testVpnLockdownUpdateAllowlist() throws Exception {
- assertConnectivity(true, "VPN is off");
+ waitForConnectivity("VPN is off");
// VPN won't start.
final Bundle restrictions = new Bundle();
@@ -149,7 +149,7 @@
// Tests that when VPN comes up, allowlisted app switches over to it.
public void testVpnLockdownAllowlistVpnComesUp() throws Exception {
- assertConnectivity(true, "VPN is off");
+ waitForConnectivity("VPN is off");
// VPN won't start initially.
final Bundle restrictions = new Bundle();
@@ -191,6 +191,23 @@
assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
}
+ private void waitForConnectivity(String message) throws InterruptedException {
+ long deadline = System.nanoTime() + CONNECTIVITY_WAIT_TIME_NS;
+ while (System.nanoTime() < deadline) {
+ try {
+ new Socket(CONNECTIVITY_CHECK_HOST, 80);
+ // Domain resolved, we have connectivity.
+ return;
+ } catch (IOException e) {
+ // Log.e(String, String, Throwable) will swallow UnknownHostException,
+ // so manually print it out here.
+ Log.e(TAG, "No connectivity yet: " + e.toString());
+ Thread.sleep(2000);
+ }
+ }
+ fail("Connectivity isn't available: " + message);
+ }
+
private void assertConnectivity(boolean shouldHaveConnectivity, String message) {
try {
new Socket(CONNECTIVITY_CHECK_HOST, 80);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
new file mode 100644
index 0000000..10971d7
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class NetworkSlicingStatusTest extends BaseDeviceAdminTest {
+ private static final String TAG = "NetworkSlicingStatusTest";
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testGetSetNetworkSlicingStatus() throws Exception {
+ // Assert default status is true
+ assertTrue(mDevicePolicyManager.isNetworkSlicingEnabled());
+
+ mDevicePolicyManager.setNetworkSlicingEnabled(false);
+ assertFalse(mDevicePolicyManager.isNetworkSlicingEnabled());
+
+ mDevicePolicyManager.setNetworkSlicingEnabled(true);
+ assertTrue(mDevicePolicyManager.isNetworkSlicingEnabled());
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
index 844b710..1362c5a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
@@ -27,7 +27,9 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.Manifest.permission;
+import android.app.admin.DevicePolicyManager;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
@@ -79,6 +81,11 @@
permission.ACCESS_BACKGROUND_LOCATION,
permission.ACCESS_COARSE_LOCATION);
+ // TODO: Augment with more permissions
+ private static final Set<String> SENSORS_PERMISSIONS = Sets.newHashSet(
+ permission.ACCESS_FINE_LOCATION);
+
+
private PermissionBroadcastReceiver mReceiver;
private UiDevice mDevice;
@@ -427,6 +434,58 @@
PERMISSION_APP_PACKAGE_NAME);
}
+ public void testSensorsRelatedPermissionsCannotBeGranted() throws Exception {
+ for (String sensorPermission: SENSORS_PERMISSIONS) {
+ // The permission cannot be granted.
+ assertFailedToSetPermissionGrantState(
+ sensorPermission, DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+ // But the user can grant it.
+ PermissionUtils.launchActivityAndRequestPermission(mReceiver, mDevice, sensorPermission,
+ PERMISSION_GRANTED, PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+
+ // And the package manager should show it as granted.
+ PermissionUtils.checkPermission(sensorPermission, PERMISSION_GRANTED,
+ PERMISSION_APP_PACKAGE_NAME);
+ }
+ }
+
+ public void testSensorsRelatedPermissionsCanBeDenied() throws Exception {
+ for (String sensorPermission: SENSORS_PERMISSIONS) {
+ // The permission can be denied
+ setPermissionGrantState(sensorPermission, PERMISSION_GRANT_STATE_DENIED);
+
+ assertPermissionGrantState(sensorPermission, PERMISSION_GRANT_STATE_DENIED);
+ assertCannotRequestPermissionFromActivity(sensorPermission);
+ }
+ }
+
+ public void testSensorsRelatedPermissionsNotGrantedViaPolicy() throws Exception {
+ setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+ for (String sensorPermission: SENSORS_PERMISSIONS) {
+ // The permission is not granted by default.
+ PermissionUtils.checkPermission(sensorPermission, PERMISSION_DENIED,
+ PERMISSION_APP_PACKAGE_NAME);
+ // But the user can grant it.
+ PermissionUtils.launchActivityAndRequestPermission(mReceiver, mDevice, sensorPermission,
+ PERMISSION_GRANTED, PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+
+ // And the package manager should show it as granted.
+ PermissionUtils.checkPermission(sensorPermission, PERMISSION_GRANTED,
+ PERMISSION_APP_PACKAGE_NAME);
+ }
+ }
+
+ private void assertFailedToSetPermissionGrantState(String permission, int value) {
+ assertTrue(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ PERMISSION_APP_PACKAGE_NAME, permission, value));
+ assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ PERMISSION_APP_PACKAGE_NAME, permission),
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+ assertEquals(mContext.getPackageManager().checkPermission(permission,
+ PERMISSION_APP_PACKAGE_NAME),
+ PackageManager.PERMISSION_DENIED);
+ }
+
private CountDownLatch initPermissionNotificationLatch() {
CountDownLatch notificationCounterLatch = new CountDownLatch(1);
NotificationListener.getInstance().addListener((notification) -> {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
index a83366a..e756984 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
@@ -22,9 +22,9 @@
import android.os.UserHandle;
import android.util.Log;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-
import com.android.bedstead.dpmwrapper.DeviceOwnerHelper;
+import com.android.cts.devicepolicy.OperationSafetyChangedCallback;
+import com.android.cts.devicepolicy.OperationSafetyChangedEvent;
public class BasicAdminReceiver extends DeviceAdminReceiver {
@@ -47,11 +47,10 @@
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- Log.d(TAG, "onReceive(userId=" + context.getUserId() + "): " + action);
-
if (DeviceOwnerHelper.runManagerMethod(this, context, intent)) return;
+ String action = intent.getAction();
+ Log.d(TAG, "onReceive(userId=" + context.getUserId() + "): " + action);
super.onReceive(context, intent);
}
@@ -88,7 +87,8 @@
@Override
public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
int networkLogsCount) {
- Log.d(TAG, "onNetworkLogsAvailable(): token=" + batchToken + ", count=" + networkLogsCount);
+ Log.d(TAG, "onNetworkLogsAvailable() on user " + context.getUserId()
+ + ": token=" + batchToken + ", count=" + networkLogsCount);
super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount);
// send the broadcast, the rest of the test happens in NetworkLoggingTest
Intent batchIntent = new Intent(ACTION_NETWORK_LOGS_AVAILABLE);
@@ -97,9 +97,20 @@
DeviceOwnerHelper.sendBroadcastToTestCaseReceiver(context, batchIntent);
}
+ @Override
+ public void onOperationSafetyStateChanged(Context context, int reason, boolean isSafe) {
+ OperationSafetyChangedEvent event = new OperationSafetyChangedEvent(reason, isSafe);
+ Log.d(TAG, "onOperationSafetyStateChanged() on user " + context.getUserId() + ": " + event);
+
+ Intent intent = OperationSafetyChangedCallback.intentFor(event);
+
+ DeviceOwnerHelper.sendBroadcastToTestCaseReceiver(context, intent);
+ }
+
private void sendUserBroadcast(Context context, String action, UserHandle userHandle) {
- Intent intent = new Intent(action);
- intent.putExtra(EXTRA_USER_HANDLE, userHandle);
- LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ Log.d(TAG, "sendUserBroadcast(): action=" + action + ", user=" + userHandle);
+ Intent intent = new Intent(action).putExtra(EXTRA_USER_HANDLE, userHandle);
+
+ DeviceOwnerHelper.sendBroadcastToTestCaseReceiver(context, intent);
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
index 136da5d..d1d8be0 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
@@ -16,14 +16,16 @@
package com.android.cts.deviceowner;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.expectThrows;
+
import android.app.Service;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.IBinder;
@@ -32,7 +34,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import android.util.DebugUtils;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
@@ -41,7 +43,6 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.Semaphore;
-import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -71,266 +72,138 @@
super.tearDown();
}
- public void testCreateAndManageUser() {
- String testUserName = "TestUser_" + System.currentTimeMillis();
+ public void testCreateAndManageUser() throws Exception {
+ UserHandle userHandle = createAndManageUser();
- UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- /* flags */ 0);
- Log.d(TAG, "User create: " + userHandle);
+ assertWithMessage("New user").that(userHandle).isNotNull();
}
- public void testCreateAndManageUser_LowStorage() {
- String testUserName = "TestUser_" + System.currentTimeMillis();
+ public void testCreateAndManageUser_LowStorage() throws Exception {
+ UserManager.UserOperationException e = expectThrows(
+ UserManager.UserOperationException.class, () -> createAndManageUser());
- try {
- mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- /* flags */ 0);
- fail("createAndManageUser should throw UserOperationException");
- } catch (UserManager.UserOperationException e) {
- assertEquals(UserManager.USER_OPERATION_ERROR_LOW_STORAGE, e.getUserOperationResult());
- }
+ assertUserOperationResult(e.getUserOperationResult(),
+ UserManager.USER_OPERATION_ERROR_LOW_STORAGE,
+ "user creation on low storage");
}
- public void testCreateAndManageUser_MaxUsers() {
- String testUserName = "TestUser_" + System.currentTimeMillis();
+ public void testCreateAndManageUser_MaxUsers() throws Exception {
+ UserManager.UserOperationException e = expectThrows(
+ UserManager.UserOperationException.class, () -> createAndManageUser());
- try {
- mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- /* flags */ 0);
- fail("createAndManageUser should throw UserOperationException");
- } catch (UserManager.UserOperationException e) {
- assertEquals(UserManager.USER_OPERATION_ERROR_MAX_USERS, e.getUserOperationResult());
- }
+ assertUserOperationResult(e.getUserOperationResult(),
+ UserManager.USER_OPERATION_ERROR_MAX_USERS,
+ "user creation when max users is reached");
}
@SuppressWarnings("unused")
private static void assertSkipSetupWizard(Context context,
DevicePolicyManager devicePolicyManager, ComponentName componentName) throws Exception {
- assertEquals("user setup not completed", 1,
- Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE));
+ assertWithMessage("user setup settings (%s)", Settings.Secure.USER_SETUP_COMPLETE)
+ .that(Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE))
+ .isEqualTo(1);
}
public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
runCrossUserVerification(DevicePolicyManager.SKIP_SETUP_WIZARD, "assertSkipSetupWizard");
+
PrimaryUserService.assertCrossUserCallArrived();
}
- public void testCreateAndManageUser_GetSecondaryUsers() {
- String testUserName = "TestUser_" + System.currentTimeMillis();
-
- UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- /* flags */ 0);
- Log.d(TAG, "User create: " + userHandle);
+ public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
+ UserHandle userHandle = createAndManageUser();
List<UserHandle> secondaryUsers = mDevicePolicyManager.getSecondaryUsers(getWho());
- assertEquals(1, secondaryUsers.size());
- assertEquals(userHandle, secondaryUsers.get(0));
+
+ assertWithMessage("wrong secondary users").that(secondaryUsers).containsExactly(userHandle);
}
public void testCreateAndManageUser_SwitchUser() throws Exception {
- LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
- getContext());
+ UserHandle userHandle = createAndManageUser();
- String testUserName = "TestUser_" + System.currentTimeMillis();
+ List<UserHandle> usersOnBroadcasts = switchUserAndWaitForBroadcasts(userHandle);
- UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- /* flags */ 0);
- Log.d(TAG, "User create: " + userHandle);
-
- LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
- localBroadcastManager.registerReceiver(broadcastReceiver,
- new IntentFilter(BasicAdminReceiver.ACTION_USER_SWITCHED));
- try {
- assertTrue(mDevicePolicyManager.switchUser(getWho(), userHandle));
- assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
- } finally {
- localBroadcastManager.unregisterReceiver(broadcastReceiver);
- }
+ assertWithMessage("user on broadcasts").that(usersOnBroadcasts).containsExactly(userHandle,
+ userHandle);
}
public void testCreateAndManageUser_CannotStopCurrentUser() throws Exception {
- LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
- getContext());
+ UserHandle userHandle = createAndManageUser();
- String testUserName = "TestUser_" + System.currentTimeMillis();
+ switchUserAndWaitForBroadcasts(userHandle);
- UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- /* flags */ 0);
- Log.d(TAG, "User create: " + userHandle);
-
- LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
- localBroadcastManager.registerReceiver(broadcastReceiver,
- new IntentFilter(BasicAdminReceiver.ACTION_USER_SWITCHED));
- try {
- assertTrue(mDevicePolicyManager.switchUser(getWho(), userHandle));
- assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
- assertEquals(UserManager.USER_OPERATION_ERROR_CURRENT_USER,
- mDevicePolicyManager.stopUser(getWho(), userHandle));
- } finally {
- localBroadcastManager.unregisterReceiver(broadcastReceiver);
- }
+ stopUserAndCheckResult(userHandle, UserManager.USER_OPERATION_ERROR_CURRENT_USER);
}
public void testCreateAndManageUser_StartInBackground() throws Exception {
- LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
- getContext());
+ UserHandle userHandle = createAndManageUser();
- String testUserName = "TestUser_" + System.currentTimeMillis();
+ List<UserHandle> usersOnBroadcasts = startUserInBackgroundAndWaitForBroadcasts(userHandle);
- UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- /* flags */ 0);
- Log.d(TAG, "User create: " + userHandle);
-
- LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
- localBroadcastManager.registerReceiver(broadcastReceiver,
- new IntentFilter(BasicAdminReceiver.ACTION_USER_STARTED));
-
- try {
- // Start user in background and wait for onUserStarted
- assertEquals(UserManager.USER_OPERATION_SUCCESS,
- mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
- assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
- } finally {
- localBroadcastManager.unregisterReceiver(broadcastReceiver);
- }
+ assertWithMessage("user on broadcasts").that(usersOnBroadcasts).containsExactly(userHandle);
}
- public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() {
- String testUserName = "TestUser_" + System.currentTimeMillis();
-
- UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- /* flags */ 0);
- Log.d(TAG, "User create: " + userHandle);
+ public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() throws Exception {
+ UserHandle userHandle = createAndManageUser();
// Start user in background and should receive max running users error
- assertEquals(UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS,
- mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
+ startUserInBackgroundAndCheckResult(userHandle,
+ UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS);
}
public void testCreateAndManageUser_StopUser() throws Exception {
- LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
- getContext());
+ UserHandle userHandle = createAndManageUser();
+ startUserInBackgroundAndWaitForBroadcasts(userHandle);
- String testUserName = "TestUser_" + System.currentTimeMillis();
+ List<UserHandle> usersOnBroadcasts = stopUserAndWaitForBroadcasts(userHandle);
- UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- /* flags */ 0);
- Log.d(TAG, "User create: " + userHandle);
- assertEquals(UserManager.USER_OPERATION_SUCCESS,
- mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
-
- LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
- localBroadcastManager.registerReceiver(broadcastReceiver,
- new IntentFilter(BasicAdminReceiver.ACTION_USER_STOPPED));
-
- try {
- assertEquals(UserManager.USER_OPERATION_SUCCESS,
- mDevicePolicyManager.stopUser(getWho(), userHandle));
- assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
- } finally {
- localBroadcastManager.unregisterReceiver(broadcastReceiver);
- }
+ assertWithMessage("user on broadcasts").that(usersOnBroadcasts).containsExactly(userHandle);
}
public void testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser() throws Exception {
- LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
- getContext());
-
- String testUserName = "TestUser_" + System.currentTimeMillis();
-
// Set DISALLOW_REMOVE_USER restriction
mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
- UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- DevicePolicyManager.MAKE_USER_EPHEMERAL);
- Log.d(TAG, "User create: " + userHandle);
- assertEquals(UserManager.USER_OPERATION_SUCCESS,
- mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
+ UserHandle userHandle = createAndManageUser(DevicePolicyManager.MAKE_USER_EPHEMERAL);
+ startUserInBackgroundAndWaitForBroadcasts(userHandle);
+ UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+ getContext(),
+ BasicAdminReceiver.ACTION_USER_STOPPED, BasicAdminReceiver.ACTION_USER_REMOVED);
- LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
- localBroadcastManager.registerReceiver(broadcastReceiver,
- new IntentFilter(BasicAdminReceiver.ACTION_USER_REMOVED));
+ callback.runAndUnregisterSelf(
+ () -> stopUserAndCheckResult(userHandle, UserManager.USER_OPERATION_SUCCESS));
- try {
- assertEquals(UserManager.USER_OPERATION_SUCCESS,
- mDevicePolicyManager.stopUser(getWho(), userHandle));
- assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
- } finally {
- localBroadcastManager.unregisterReceiver(broadcastReceiver);
- // Clear DISALLOW_REMOVE_USER restriction
- mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
- }
+ // It's running just one operation (which issues a ACTION_USER_STOPPED), but as the
+ // user is ephemeral, it will be automatically removed (which issues a
+ // ACTION_USER_REMOVED).
+ assertWithMessage("user on broadcasts").that(callback.getUsersOnReceivedBroadcasts())
+ .containsExactly(userHandle, userHandle);
}
@SuppressWarnings("unused")
private static void logoutUser(Context context, DevicePolicyManager devicePolicyManager,
ComponentName componentName) {
- assertEquals("cannot logout user", UserManager.USER_OPERATION_SUCCESS,
- devicePolicyManager.logoutUser(componentName));
+ assertUserOperationResult(devicePolicyManager.logoutUser(componentName),
+ UserManager.USER_OPERATION_SUCCESS, "cannot logout user");
}
public void testCreateAndManageUser_LogoutUser() throws Exception {
- LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
- getContext());
+ UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+ getContext(),
+ BasicAdminReceiver.ACTION_USER_STARTED, BasicAdminReceiver.ACTION_USER_STOPPED);
- LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
- localBroadcastManager.registerReceiver(broadcastReceiver,
- new IntentFilter(BasicAdminReceiver.ACTION_USER_STOPPED));
+ UserHandle userHandle = runCrossUserVerification(callback,
+ /* createAndManageUserFlags= */ 0, "logoutUser");
- try {
- UserHandle userHandle = runCrossUserVerification(
- /* createAndManageUserFlags */ 0, "logoutUser");
- assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
- } finally {
- localBroadcastManager.unregisterReceiver(broadcastReceiver);
- }
+ assertWithMessage("user on broadcasts").that(callback.getUsersOnReceivedBroadcasts())
+ .containsExactly(userHandle, userHandle);
}
@SuppressWarnings("unused")
private static void assertAffiliatedUser(Context context,
DevicePolicyManager devicePolicyManager, ComponentName componentName) {
- assertTrue("not affiliated user", devicePolicyManager.isAffiliatedUser());
+ assertWithMessage("affiliated user").that(devicePolicyManager.isAffiliatedUser()).isTrue();
}
public void testCreateAndManageUser_Affiliated() throws Exception {
@@ -341,7 +214,8 @@
@SuppressWarnings("unused")
private static void assertEphemeralUser(Context context,
DevicePolicyManager devicePolicyManager, ComponentName componentName) {
- assertTrue("not ephemeral user", devicePolicyManager.isEphemeralUser(componentName));
+ assertWithMessage("ephemeral user").that(devicePolicyManager.isEphemeralUser(componentName))
+ .isTrue();
}
public void testCreateAndManageUser_Ephemeral() throws Exception {
@@ -377,7 +251,13 @@
PrimaryUserService.assertCrossUserCallArrived();
}
- private UserHandle runCrossUserVerification(int createAndManageUserFlags, String methodName) {
+ private UserHandle runCrossUserVerification(int createAndManageUserFlags, String methodName)
+ throws Exception {
+ return runCrossUserVerification(/* callback= */ null, createAndManageUserFlags, methodName);
+ }
+
+ private UserHandle runCrossUserVerification(UserActionCallback callback,
+ int createAndManageUserFlags, String methodName) throws Exception {
String testUserName = "TestUser_" + System.currentTimeMillis();
// Set affiliation id to allow communication.
@@ -394,75 +274,161 @@
SecondaryUserAdminReceiver.getComponentName(getContext()),
bundle,
createAndManageUserFlags);
- Log.d(TAG, "User create: " + userHandle);
- assertEquals(UserManager.USER_OPERATION_SUCCESS,
- mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
-
+ Log.d(TAG, "User created: " + userHandle);
+ assertWithMessage("user with name %s", testUserName).that(userHandle).isNotNull();
+ if (callback != null) {
+ startUserInBackgroundAndWaitForBroadcasts(callback, userHandle);
+ } else {
+ startUserInBackgroundAndWaitForBroadcasts(userHandle);
+ }
return userHandle;
}
// createAndManageUser should circumvent the DISALLOW_ADD_USER restriction
- public void testCreateAndManageUser_AddRestrictionSet() {
+ public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_ADD_USER);
- UserHandle userHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User",
- getWho(), null, 0);
- assertNotNull(userHandle);
+ createAndManageUser();
}
- public void testCreateAndManageUser_RemoveRestrictionSet() {
+ public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
- UserHandle userHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User",
- getWho(), null, 0);
- assertNotNull(userHandle);
+ UserHandle userHandle = createAndManageUser();
- boolean removed = mDevicePolicyManager.removeUser(getWho(), userHandle);
// When the device owner itself has set the user restriction, it should still be allowed
// to remove a user.
- assertTrue(removed);
+ List<UserHandle> usersOnBroadcasts = removeUserAndWaitForBroadcasts(userHandle);
+
+ assertWithMessage("user on broadcasts").that(usersOnBroadcasts).containsExactly(userHandle);
}
- public void testUserAddedOrRemovedBroadcasts() throws InterruptedException {
- LocalBroadcastReceiver receiver = new LocalBroadcastReceiver();
- LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
- getContext());
- localBroadcastManager.registerReceiver(receiver,
- new IntentFilter(BasicAdminReceiver.ACTION_USER_ADDED));
- UserHandle userHandle;
- try {
- userHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
- null, 0);
- assertNotNull(userHandle);
- assertEquals(userHandle, receiver.waitForBroadcastReceived());
- } finally {
- localBroadcastManager.unregisterReceiver(receiver);
- }
- localBroadcastManager.registerReceiver(receiver,
- new IntentFilter(BasicAdminReceiver.ACTION_USER_REMOVED));
- try {
- assertTrue(mDevicePolicyManager.removeUser(getWho(), userHandle));
- assertEquals(userHandle, receiver.waitForBroadcastReceived());
- } finally {
- localBroadcastManager.unregisterReceiver(receiver);
- }
+ public void testUserAddedOrRemovedBroadcasts() throws Exception {
+ UserHandle userHandle = createAndManageUser();
+
+ List<UserHandle> userHandles = removeUserAndWaitForBroadcasts(userHandle);
+
+ assertWithMessage("user on broadcasts").that(userHandles).containsExactly(userHandle);
}
- static class LocalBroadcastReceiver extends BroadcastReceiver {
- private LinkedBlockingQueue<UserHandle> mQueue = new LinkedBlockingQueue<>(1);
+ private UserHandle createAndManageUser() throws Exception {
+ return createAndManageUser(/* flags= */ 0);
+ }
- @Override
- public void onReceive(Context context, Intent intent) {
- UserHandle userHandle = intent.getParcelableExtra(BasicAdminReceiver.EXTRA_USER_HANDLE);
- Log.d(TAG, "broadcast receiver received " + intent + " with userHandle "
- + userHandle);
- mQueue.offer(userHandle);
+ private UserHandle createAndManageUser(int flags) throws Exception {
+ String testUserName = "TestUser_" + System.currentTimeMillis();
- }
+ UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+ getContext(), BasicAdminReceiver.ACTION_USER_ADDED);
- UserHandle waitForBroadcastReceived() throws InterruptedException {
- return mQueue.poll(BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
- }
+ UserHandle userHandle = callback.callAndUnregisterSelf(() ->
+ mDevicePolicyManager.createAndManageUser(
+ /* admin= */ getWho(),
+ testUserName,
+ /* profileOwner= */ getWho(),
+ /* adminExtras= */ null,
+ flags));
+ Log.d(TAG, "User '" + testUserName + "' created: " + userHandle);
+
+ return userHandle;
+ }
+
+ /**
+ * Switches to the given user, or fails if the user could not be switched or if the expected
+ * broadcasts were not received in time.
+ *
+ * @return users received in the broadcasts
+ */
+ private List<UserHandle> switchUserAndWaitForBroadcasts(UserHandle userHandle)
+ throws Exception {
+ Log.d(TAG, "Switching to user " + userHandle);
+
+ UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+ getContext(),
+ BasicAdminReceiver.ACTION_USER_STARTED, BasicAdminReceiver.ACTION_USER_SWITCHED);
+
+ callback.runAndUnregisterSelf(() -> {
+ boolean switched = mDevicePolicyManager.switchUser(getWho(), userHandle);
+ assertWithMessage("switched to user %s", userHandle).that(switched).isTrue();
+ });
+ return callback.getUsersOnReceivedBroadcasts();
+ }
+
+ /**
+ * Removes the given user, or fails if the user could not be removed or if the expected
+ * broadcasts were not received in time.
+ *
+ * @return users received in the broadcasts
+ */
+ private List<UserHandle> removeUserAndWaitForBroadcasts(UserHandle userHandle)
+ throws Exception {
+ UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+ getContext(), BasicAdminReceiver.ACTION_USER_REMOVED);
+
+ callback.runAndUnregisterSelf(() -> {
+ boolean removed = mDevicePolicyManager.removeUser(getWho(), userHandle);
+ assertWithMessage("removed user %s", userHandle).that(removed).isTrue();
+ });
+
+ return callback.getUsersOnReceivedBroadcasts();
+ }
+
+ private static String userOperationResultToString(int result) {
+ return DebugUtils.constantToString(UserManager.class, "USER_OPERATION_", result);
+ }
+
+ private static void assertUserOperationResult(int actualResult, int expectedResult,
+ String operationFormat, Object... operationArgs) {
+ String operation = String.format(operationFormat, operationArgs);
+ assertWithMessage("result for %s (%s instead of %s)", operation,
+ userOperationResultToString(actualResult),
+ userOperationResultToString(expectedResult))
+ .that(actualResult).isEqualTo(expectedResult);
+ }
+
+ private void startUserInBackgroundAndCheckResult(UserHandle userHandle, int expectedResult) {
+ int actualResult = mDevicePolicyManager.startUserInBackground(getWho(), userHandle);
+ assertUserOperationResult(actualResult, expectedResult, "starting user %s in background",
+ userHandle);
+ }
+
+ /**
+ * Starts the given user in background, or fails if the user could not be started or if the
+ * expected broadcasts were not received in time.
+ *
+ * @return users received in the broadcasts
+ */
+ private List<UserHandle> startUserInBackgroundAndWaitForBroadcasts(UserHandle userHandle)
+ throws Exception {
+ UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+ getContext(), BasicAdminReceiver.ACTION_USER_STARTED);
+ return startUserInBackgroundAndWaitForBroadcasts(callback, userHandle);
+ }
+
+ private List<UserHandle> startUserInBackgroundAndWaitForBroadcasts(UserActionCallback callback,
+ UserHandle userHandle) throws Exception {
+ callback.runAndUnregisterSelf(() -> startUserInBackgroundAndCheckResult(userHandle,
+ UserManager.USER_OPERATION_SUCCESS));
+ return callback.getUsersOnReceivedBroadcasts();
+ }
+
+ private void stopUserAndCheckResult(UserHandle userHandle, int expectedResult) {
+ int actualResult = mDevicePolicyManager.stopUser(getWho(), userHandle);
+ assertUserOperationResult(actualResult, expectedResult, "stopping user %s", userHandle);
+ }
+
+ /**
+ * Stops the given user, or fails if the user could not be stop or if the expected broadcasts
+ * were not received in time.
+ *
+ * @return users received in the broadcasts
+ */
+ private List<UserHandle> stopUserAndWaitForBroadcasts(UserHandle userHandle) throws Exception {
+ UserActionCallback callback = UserActionCallback.getCallbackForBroadcastActions(
+ getContext(), BasicAdminReceiver.ACTION_USER_STOPPED);
+ callback.runAndUnregisterSelf(
+ () -> stopUserAndCheckResult(userHandle, UserManager.USER_OPERATION_SUCCESS));
+ return callback.getUsersOnReceivedBroadcasts();
}
public static final class PrimaryUserService extends Service {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
index 86ccea4..922745d 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
@@ -242,4 +242,25 @@
public void testAllOperations() {
mTester.testAllOperations(mDevicePolicyManager, getWho());
}
+
+ /**
+ * Tests {@link DevicePolicyManager#isSafeOperation(int)}.
+ */
+ public void testIsSafeOperation() {
+ mTester.testIsSafeOperation(mDevicePolicyManager);
+ }
+
+ /**
+ * Tests {@link android.app.admin.UnsafeStateException} properties.
+ */
+ public void testUnsafeStateException() {
+ mTester.testUnsafeStateException(mDevicePolicyManager, getWho());
+ }
+
+ /**
+ * Tests {@link android.app.admin.DeviceAdminReceiver#onOperationSafetyStateChanged()}.
+ */
+ public void testOnOperationSafetyStateChanged() {
+ mTester.testOnOperationSafetyStateChanged(mContext, mDevicePolicyManager);
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java
index 10f0040..fff65d6 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java
@@ -27,6 +27,7 @@
import android.os.UserManager;
import android.util.Log;
+//TODO(b/174859111): move to automotive specific module
/**
* Device owner tests specific for devices that use
* {@link android.os.UserManager#isHeadlessSystemUserMode()}.
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ListForegroundAffiliatedUsersTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ListForegroundAffiliatedUsersTest.java
new file mode 100644
index 0000000..65d2261
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ListForegroundAffiliatedUsersTest.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.cts.deviceowner;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.ActivityManager;
+import android.os.UserHandle;
+
+import java.util.List;
+
+public final class ListForegroundAffiliatedUsersTest extends BaseDeviceOwnerTest {
+
+ public void testListForegroundAffiliatedUsers_onlyForegroundUser() throws Exception {
+ List<UserHandle> users = mDevicePolicyManager.listForegroundAffiliatedUsers();
+
+ assertWithMessage("foreground users").that(users).hasSize(1);
+ UserHandle currentUser = users.get(0);
+ assertWithMessage("current foreground user").that(currentUser).isNotNull();
+ assertWithMessage("current foreground user id").that(currentUser.getIdentifier())
+ .isEqualTo(ActivityManager.getCurrentUser());
+ }
+
+ public void testListForegroundAffiliatedUsers_empty() throws Exception {
+ List<UserHandle> users = mDevicePolicyManager.listForegroundAffiliatedUsers();
+ assertWithMessage("foreground users").that(users).isEmpty();
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java
index 139db12..450b996 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java
@@ -15,6 +15,8 @@
*/
package com.android.cts.deviceowner;
+import static org.testng.Assert.assertThrows;
+
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.test.AndroidTestCase;
@@ -41,7 +43,8 @@
}
public void testIsProvisioningAllowedFalse() {
- assertFalse(mDevicePolicyManager.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE));
+ assertFalse(mDevicePolicyManager
+ .isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE));
}
public void testIsProvisioningNotAllowedForManagedProfileAction() {
@@ -49,4 +52,8 @@
.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE));
}
+ public void testListForegroundAffiliatedUsers_notDeviceOwner() throws Exception {
+ assertThrows(SecurityException.class,
+ () -> mDevicePolicyManager.listForegroundAffiliatedUsers());
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserActionCallback.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserActionCallback.java
new file mode 100644
index 0000000..a11602d
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserActionCallback.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceowner;
+
+import static com.android.bedstead.dpmwrapper.TestAppHelper.registerTestCaseReceiver;
+import static com.android.bedstead.dpmwrapper.TestAppHelper.unregisterTestCaseReceiver;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class used to wait for user-related intents broadcast by {@link BasicAdminReceiver}.
+ *
+ */
+final class UserActionCallback {
+
+ private static final String TAG = UserActionCallback.class.getSimpleName();
+
+ private static final int BROADCAST_TIMEOUT = 300_000;
+
+ private final int mExpectedSize;
+ private final List<String> mExpectedActions;
+ private final List<String> mPendingActions;
+
+ private final List<UserHandle> mReceivedUsers = new ArrayList<>();
+ private final CountDownLatch mLatch;
+ private final Context mContext;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (intent.hasExtra(BasicAdminReceiver.EXTRA_USER_HANDLE)) {
+ UserHandle userHandle = intent
+ .getParcelableExtra(BasicAdminReceiver.EXTRA_USER_HANDLE);
+ Log.d(TAG, "broadcast receiver received " + action + " with user " + userHandle);
+ mReceivedUsers.add(userHandle);
+ } else {
+ Log.e(TAG, "broadcast receiver received " + intent.getAction()
+ + " WITHOUT " + BasicAdminReceiver.EXTRA_USER_HANDLE + " extra");
+ }
+ boolean removed = mPendingActions.remove(action);
+ if (!removed) {
+ Log.e(TAG, "Unexpected action " + action + "; what's left is " + mPendingActions);
+ return;
+ }
+ Log.d(TAG, "Counting down latch (current count is " + mLatch.getCount() + ")");
+ mLatch.countDown();
+ }
+ };
+
+ private UserActionCallback(Context context, String... actions) {
+ mContext = context;
+ mExpectedSize = actions.length;
+ mExpectedActions = new ArrayList<>(mExpectedSize);
+ mPendingActions = new ArrayList<>(mExpectedSize);
+ for (String action : actions) {
+ mExpectedActions.add(action);
+ mPendingActions.add(action);
+ }
+ mLatch = new CountDownLatch(mExpectedSize);
+ }
+
+ /**
+ * Creates a new {@link UserActionCallback} and registers it to receive user broadcasts in the
+ * given context.
+ *
+ * @param context context to register for.
+ * @param actions expected actions.
+ *
+ * @return a new {@link UserActionCallback}.
+ */
+ public static UserActionCallback getCallbackForBroadcastActions(Context context,
+ String... actions) {
+ UserActionCallback callback = new UserActionCallback(context, actions);
+
+ IntentFilter filter = new IntentFilter();
+ for (String action : actions) {
+ filter.addAction(action);
+ }
+
+ registerTestCaseReceiver(context, callback.mReceiver, filter);
+
+ return callback;
+ }
+
+ /**
+ * Runs the given operation, blocking until the broadcasts are received and automatically
+ * unregistering itself at the end.
+ *
+ * @param runnable operation to run.
+ *
+ * @return operation result.
+ */
+ public <V> V callAndUnregisterSelf(Callable<V> callable)
+ throws Exception {
+ try {
+ return callable.call();
+ } finally {
+ unregisterSelf();
+ }
+ }
+
+ /**
+ * Gets the list of {@link UserHandle} associated with the broadcasts received so far.
+ */
+ public List<UserHandle> getUsersOnReceivedBroadcasts() {
+ return Collections.unmodifiableList(new ArrayList<>(mReceivedUsers));
+ }
+
+ /**
+ * Runs the given operation, blocking until the broadcasts are received and automatically
+ * unregistering itself at the end.
+ *
+ * @param runnable operation to run.
+ *
+ * @return operation result.
+ */
+ public void runAndUnregisterSelf(ThrowingRunnable runnable) throws Exception {
+ try {
+ runnable.run();
+ waitForBroadcasts();
+ } finally {
+ unregisterSelf();
+ }
+ }
+
+ /**
+ * Unregister itself as a {@link BroadcastReceiver} for user events.
+ */
+ private void unregisterSelf() {
+ unregisterTestCaseReceiver(mContext, mReceiver);
+ }
+
+ /**
+ * Custom {@link Runnable} that throws an {@link Exception}.
+ */
+ interface ThrowingRunnable {
+ void run() throws Exception;
+ }
+
+ private void waitForBroadcasts() throws Exception {
+ Log.d(TAG, "Waiting up to " + BROADCAST_TIMEOUT + " to receive " + mExpectedSize
+ + " broadcasts");
+ boolean received = mLatch.await(BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+ try {
+ assertWithMessage("%s messages received in %s ms. Expected actions=%s, "
+ + "pending=%s", mExpectedSize, BROADCAST_TIMEOUT, mExpectedActions,
+ mPendingActions).that(received).isTrue();
+ } catch (Exception | Error e) {
+ Log.e(TAG, "waitForBroadcasts() failed: " + e);
+ throw e;
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java
new file mode 100644
index 0000000..7230023
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.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.cts.deviceowner;
+
+
+import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_NONE;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+
+import com.android.compatibility.common.util.WifiConfigCreator;
+
+import java.util.List;
+
+public class WifiNetworkConfigurationWithoutFineLocationPermissionTest extends BaseDeviceOwnerTest {
+ private static final String TAG = "WifiNetworkConfigurationWithoutFineLocationPermissionTest";
+
+ // Unique SSID to use for this test (max SSID length is 32)
+ private static final String NETWORK_SSID = "com.android.cts.abcdefghijklmnop";
+ private static final int INVALID_NETWORK_ID = -1;
+
+ private WifiManager mWifiManager;
+ private WifiConfigCreator mWifiConfigCreator;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mWifiConfigCreator = new WifiConfigCreator(getContext());
+ mWifiManager = getContext().getSystemService(WifiManager.class);
+ }
+
+ public void testAddAndRetrieveCallerConfiguredNetworks() throws Exception {
+ assertTrue("WiFi is not enabled", mWifiManager.isWifiEnabled());
+ assertEquals(PackageManager.PERMISSION_DENIED,
+ mContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION));
+
+ int netId = mWifiConfigCreator.addNetwork(NETWORK_SSID, /* hidden */ false,
+ SECURITY_TYPE_NONE, /* password */ null);
+ assertNotSame("Failed to add network", INVALID_NETWORK_ID, netId);
+
+ try {
+ List<WifiConfiguration> configs = mWifiManager.getCallerConfiguredNetworks();
+ assertEquals(1, configs.size());
+ assertEquals('"' + NETWORK_SSID + '"', configs.get(0).SSID);
+ } finally {
+ mWifiManager.removeNetwork(netId);
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
index 0b6c1b7..5d36980 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
@@ -40,8 +40,8 @@
(DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
boolean inManagedProfile = dpm.isProfileOwnerApp("com.android.cts.managedprofile");
- Log.i(TAG, "activity " + className + " started on user " + getUserId()
- + ", is in managed profile: " + inManagedProfile);
+ Log.i(TAG, "activity " + className + " started, is in managed profile: "
+ + inManagedProfile);
Intent result = new Intent();
result.putExtra("extra_receiver_class", className);
result.putExtra("extra_in_managed_profile", inManagedProfile);
diff --git a/hostsidetests/devicepolicy/app/IntentSender/Android.bp b/hostsidetests/devicepolicy/app/IntentSender/Android.bp
index 4063eb1..9948be7 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/Android.bp
+++ b/hostsidetests/devicepolicy/app/IntentSender/Android.bp
@@ -24,6 +24,7 @@
"ctstestrunner-axt",
"ub-uiautomator",
"androidx.legacy_legacy-support-v4",
+ "truth-prebuilt",
],
platform_apis: true,
// tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
index eef1577..23860f3 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
@@ -16,12 +16,17 @@
package com.android.cts.intent.sender;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.test.InstrumentationTestCase;
+import java.util.List;
+
public class AppLinkTest extends InstrumentationTestCase {
private static final String TAG = "AppLinkTest";
@@ -75,19 +80,25 @@
throws Exception {
PackageManager pm = mContext.getPackageManager();
- Intent result = mActivity.getResult(getHttpIntent());
- assertNotNull(result);
+ Intent intent = getHttpIntent();
+ Intent result = mActivity.getResult(intent);
+ assertWithMessage("result for intent %s", intent).that(result).isNotNull();
// If it is received in the other profile, we cannot check the class from the ResolveInfo
// returned by queryIntentActivities. So we rely on the receiver telling us its class.
- assertEquals(receiverClassName, result.getStringExtra(EXTRA_RECEIVER_CLASS));
- assertTrue(result.hasExtra(EXTRA_IN_MANAGED_PROFILE));
- assertEquals(inManagedProfile, result.getBooleanExtra(EXTRA_IN_MANAGED_PROFILE, false));
+ assertWithMessage("extra %s on intent %s", EXTRA_RECEIVER_CLASS, result)
+ .that(result.getStringExtra(EXTRA_RECEIVER_CLASS)).isEqualTo(receiverClassName);
+ assertWithMessage("has extra %s on intent %s", EXTRA_IN_MANAGED_PROFILE, result)
+ .that(result.hasExtra(EXTRA_IN_MANAGED_PROFILE)).isTrue();
+ assertWithMessage("extra %s on intent %s", EXTRA_IN_MANAGED_PROFILE, result)
+ .that(result.getBooleanExtra(EXTRA_IN_MANAGED_PROFILE, false))
+ .isEqualTo(inManagedProfile);
}
private void assertNumberOfReceivers(int n) {
PackageManager pm = mContext.getPackageManager();
- assertEquals(n, pm.queryIntentActivities(getHttpIntent(), /* flags = */ 0).size());
+ List<ResolveInfo> receivers = pm.queryIntentActivities(getHttpIntent(), /* flags = */ 0);
+ assertWithMessage("receivers").that(receivers).hasSize(n);
}
private Intent getHttpIntent() {
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
index 8492fcc..dddf76c 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
@@ -15,6 +15,8 @@
*/
package com.android.cts.profileowner;
+import android.app.admin.DevicePolicyManager;
+
import com.android.cts.devicepolicy.DevicePolicySafetyCheckerIntegrationTester;
// TODO(b/174859111): move to automotive-only section
@@ -33,4 +35,25 @@
public void testAllOperations() {
mTester.testAllOperations(mDevicePolicyManager, getWho());
}
+
+ /**
+ * Tests {@link DevicePolicyManager#isSafeOperation(int)}.
+ */
+ public void testIsSafeOperation() {
+ mTester.testIsSafeOperation(mDevicePolicyManager);
+ }
+
+ /**
+ * Tests {@link android.app.admin.UnsafeStateException} properties.
+ */
+ public void testUnsafeStateException() {
+ mTester.testUnsafeStateException(mDevicePolicyManager, getWho());
+ }
+
+ /**
+ * Tests {@link android.app.admin.DeviceAdminReceiver#onOperationSafetyStateChanged()}.
+ */
+ public void testOnOperationSafetyStateChanged() {
+ mTester.testOnOperationSafetyStateChanged(mContext, mDevicePolicyManager);
+ }
}
diff --git a/hostsidetests/devicepolicy/app/common/Android.bp b/hostsidetests/devicepolicy/app/common/Android.bp
index 95cadf6..68aa228 100644
--- a/hostsidetests/devicepolicy/app/common/Android.bp
+++ b/hostsidetests/devicepolicy/app/common/Android.bp
@@ -24,5 +24,7 @@
"ctstestrunner-axt",
"androidx.test.rules",
"ub-uiautomator",
- ],
+ "testng", // TODO: remove once Android migrates to JUnit 4.13, which has assertThrows
+ "DpmWrapper",
+ ],
}
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
index fd841ca..1dbac7d 100644
--- a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
@@ -29,11 +29,16 @@
import static android.app.admin.DevicePolicyManager.operationSafetyReasonToString;
import static android.app.admin.DevicePolicyManager.operationToString;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import static org.junit.Assert.fail;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
import android.app.admin.DevicePolicyManager;
import android.app.admin.UnsafeStateException;
import android.content.ComponentName;
+import android.content.Context;
import android.os.UserManager;
import android.util.Log;
@@ -75,6 +80,7 @@
* Tests that all safety-aware operations are properly implemented.
*/
public final void testAllOperations(DevicePolicyManager dpm, ComponentName admin) {
+ Log.d(TAG, "testAllOperations: dpm=" + dpm + ", admin=" + admin);
Objects.requireNonNull(dpm);
List<String> failures = new ArrayList<>();
@@ -100,6 +106,88 @@
}
/**
+ * Tests {@link DevicePolicyManager#isSafeOperation(int)}.
+ */
+ public void testIsSafeOperation(DevicePolicyManager dpm) {
+ // Currently there's just one reason...
+ int reason = OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+ Log.d(TAG, "testIsSafeOperation(): dpm=" + dpm + ", reason="
+ + operationSafetyReasonToString(reason));
+ Objects.requireNonNull(dpm);
+ assertOperationSafety(dpm, reason, /* isSafe= */ true);
+
+ setOperationUnsafe(dpm, OPERATION_LOCK_NOW, reason);
+
+ assertOperationSafety(dpm, reason, /* isSafe= */ false);
+ }
+
+ private void assertOperationSafety(DevicePolicyManager dpm, int reason, boolean isSafe) {
+ assertWithMessage("%s safety", operationSafetyReasonToString(reason))
+ .that(dpm.isSafeOperation(reason)).isEqualTo(isSafe);
+ }
+
+ /**
+ * Tests {@link UnsafeStateException} properties.
+ */
+ public void testUnsafeStateException(DevicePolicyManager dpm, ComponentName admin) {
+ // Currently there's just one reason...
+ int reason = OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+ // Operation doesn't really matter
+ int operation = OPERATION_LOCK_NOW;
+ Log.d(TAG, "testUnsafeStateException(): dpm=" + dpm + ", admin=" + admin
+ + ", reason=" + operationSafetyReasonToString(reason)
+ + ", operation=" + operationToString(operation));
+
+ setOperationUnsafe(dpm, operation, reason);
+ UnsafeStateException e = expectThrows(UnsafeStateException.class,
+ () -> runCommonOrSpecificOperation(dpm, admin, operation, /* overloaded= */ false));
+
+ int actualOperation = e.getOperation();
+ assertWithMessage("operation (%s)", operationToString(actualOperation))
+ .that(actualOperation).isEqualTo(operation);
+ List<Integer> actualReasons = e.getReasons();
+ assertWithMessage("reasons").that(actualReasons).hasSize(1);
+ int actualReason = actualReasons.get(0);
+ assertWithMessage("reason (%s)", operationSafetyReasonToString(actualReason))
+ .that(actualReason).isEqualTo(reason);
+ }
+
+ /**
+ * Tests {@link android.app.admin.DeviceAdminReceiver#onOperationSafetyStateChanged()}.
+ */
+ public void testOnOperationSafetyStateChanged(Context context, DevicePolicyManager dpm) {
+ // Currently there's just one reason...
+ int reason = OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+ // Operation doesn't really matter
+ int operation = OPERATION_LOCK_NOW;
+ Log.d(TAG, "testOnOperationSafetyStateChanged(): dpm=" + dpm
+ + ", reason=" + operationSafetyReasonToString(reason)
+ + ", operation=" + operationToString(operation));
+ OperationSafetyChangedCallback receiver = OperationSafetyChangedCallback.register(context);
+ try {
+ setOperationUnsafe(dpm, operation, reason);
+ // Must force OneTimeSafetyChecker to generate the event by calling the unsafe operation
+ assertThrows(UnsafeStateException.class, () -> dpm.lockNow());
+
+ assertNextEvent(receiver, reason, /* isSafe= */ false);
+
+ // OneTimeSafetyChecker automatically disables itself after one operation, which in turn
+ // triggers another event
+ assertNextEvent(receiver, reason, /* isSafe= */ true);
+ } finally {
+ receiver.unregister(context);
+ }
+ }
+
+ private void assertNextEvent(OperationSafetyChangedCallback receiver,
+ int reason, boolean isSafe) {
+ OperationSafetyChangedEvent event = receiver.getNextEvent();
+ Log.v(TAG, "Received event: " + event);
+ assertWithMessage("event (%s) reason", event).that(event.reason).isEqualTo(reason);
+ assertWithMessage("event (%s) safety state", event).that(event.isSafe).isEqualTo(isSafe);
+ }
+
+ /**
* Gets the device / profile owner-specific operations.
*
* <p>By default it returns an empty array, but sub-classes can override to add its supported
@@ -150,35 +238,13 @@
// Currently there's just one reason...
int reason = OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
- if (!dpm.isSafeOperation(reason)) {
- failures.add("Operation " + name + " should be safe");
- return;
- }
try {
setOperationUnsafe(dpm, operation, reason);
- if (dpm.isSafeOperation(reason)) {
- failures.add("Operation " + name + " should be unsafe");
- return;
- }
runCommonOrSpecificOperation(dpm, admin, operation, overloaded);
Log.e(TAG, name + " didn't throw an UnsafeStateException");
failures.add(name);
} catch (UnsafeStateException e) {
Log.d(TAG, name + " failed as expected: " + e);
- List<Integer> actualReasons = e.getReasons();
- if (actualReasons.size() != 1) {
- failures.add(String.format("received invalid number of reasons (%s); expected just "
- + "1 (%d - %s)", actualReasons, reason,
- operationSafetyReasonToString(reason)));
-
- } else {
- int actualReason = actualReasons.get(0);
- if (actualReason != reason) {
- failures.add(String.format("received exception with reason %s instead of %s",
- operationSafetyReasonToString(actualReason),
- operationSafetyReasonToString(reason)));
- }
- }
} catch (Exception e) {
Log.e(TAG, name + " threw unexpected exception", e);
failures.add(name + "(" + e + ")");
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedCallback.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedCallback.java
new file mode 100644
index 0000000..fd2f2ab
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedCallback.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 com.android.cts.devicepolicy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import com.android.bedstead.dpmwrapper.TestAppHelper;
+
+import junit.framework.AssertionFailedError;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+//TODO(b/174859111): move to automotive-only section
+/**
+ * Helper class used by test apps to get the safety event received by the device owner's
+ * {@link android.app.admin.DeviceAdminReceiver}.
+ */
+public final class OperationSafetyChangedCallback {
+
+ private static final String TAG = OperationSafetyChangedCallback.class.getSimpleName();
+
+ private static final String ACTION_STATE_CHANGED = "operation_safety_state_changed";
+ private static final String EXTRA_REASON = "reason";
+ private static final String EXTRA_IS_SAFE = "is_safe";
+
+ private static final long TIMEOUT_MS = 50_000;
+
+ private final LinkedBlockingQueue<OperationSafetyChangedEvent> mEvents =
+ new LinkedBlockingQueue<>();
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (!ACTION_STATE_CHANGED.equals(action)) {
+ Log.e(TAG, "Invalid action " + action + " on intent " + intent);
+ return;
+ }
+ if (!intent.hasExtra(EXTRA_REASON)) {
+ Log.e(TAG, "No " + EXTRA_REASON + " extra on intent " + intent);
+ return;
+ }
+ if (!intent.hasExtra(EXTRA_IS_SAFE)) {
+ Log.e(TAG, "No " + EXTRA_IS_SAFE + " extra on intent " + intent);
+ return;
+ }
+ OperationSafetyChangedEvent event = new OperationSafetyChangedEvent(
+ intent.getIntExtra(EXTRA_REASON, 42),
+ intent.getBooleanExtra(EXTRA_IS_SAFE, false));
+ Log.d(TAG, "Received intent with event " + event + " on user " + context.getUserId());
+ mEvents.offer(event);
+ }
+ };
+
+ private OperationSafetyChangedCallback() {}
+
+ /**
+ * Creates and registers a callback in the given context.
+ */
+ public static OperationSafetyChangedCallback register(Context context) {
+ Log.d(TAG, "Registering " + ACTION_STATE_CHANGED + " on user " + context.getUserId());
+ OperationSafetyChangedCallback callback = new OperationSafetyChangedCallback();
+ TestAppHelper.registerTestCaseReceiver(context, callback.mReceiver,
+ new IntentFilter(ACTION_STATE_CHANGED));
+ return callback;
+ }
+
+ /**
+ * Unregister this callback in the given context.
+ */
+ public void unregister(Context context) {
+ Log.d(TAG, "Unregistering " + mReceiver + " on user " + context.getUserId());
+ TestAppHelper.unregisterTestCaseReceiver(context, mReceiver);
+ }
+
+ /**
+ * Gets the intent for the given event.
+ */
+ public static Intent intentFor(OperationSafetyChangedEvent event) {
+ return new Intent(ACTION_STATE_CHANGED)
+ .putExtra(EXTRA_REASON, event.reason)
+ .putExtra(EXTRA_IS_SAFE, event.isSafe);
+ }
+
+ /**
+ * Gets next event or fail.
+ */
+ public OperationSafetyChangedEvent getNextEvent() {
+ OperationSafetyChangedEvent event = null;
+ try {
+ event = mEvents.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ String msg = "interrupted waiting for event";
+ Log.e(TAG, msg, e);
+ Thread.currentThread().interrupt();
+ throw new AssertionFailedError(msg);
+ }
+ if (event == null) {
+ String msg = "didn't receive an OperationSafetyChangedEvent in "
+ + TIMEOUT_MS + "ms on " + this;
+ Log.e(TAG, msg);
+ throw new AssertionFailedError(msg);
+ }
+ return event;
+ }
+
+ @Override
+ public String toString() {
+ return "OperationSafetyChangedCallback[events=" + mEvents + "]";
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedEvent.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedEvent.java
new file mode 100644
index 0000000..c61e732
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/OperationSafetyChangedEvent.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 com.android.cts.devicepolicy;
+
+import android.app.admin.DevicePolicyManager;
+
+//TODO(b/174859111): move to automotive-only section
+/**
+ * Represents an operation safety event received by a
+ * {@link android.app.admin.DeviceAdminReceiver#onOperationSafetyStateChanged(
+ * android.content.Context, int, boolean)}.
+ *
+ * <p>NOTE: doesn't implement {@code Parcelable} because it needs to cross process boundaries on
+ * automotive, which trows a {@code ClassNotFoundException} in the receiving end - it't not worth
+ * to fix that...
+ */
+public final class OperationSafetyChangedEvent {
+
+ public final int reason;
+ public final boolean isSafe;
+
+ public OperationSafetyChangedEvent(int reason, boolean isSafe) {
+ this.reason = reason;
+ this.isSafe = isSafe;
+ }
+
+ @Override
+ public String toString() {
+ return "OperationSafetyChangedEvent["
+ + DevicePolicyManager.operationSafetyReasonToString(reason) + ": "
+ + (isSafe ? "SAFE" : "UNSAFE") + ']';
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
index 044e356..06ed3ab 100644
--- a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
@@ -19,10 +19,11 @@
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
+import android.Manifest;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
@@ -33,10 +34,24 @@
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
+import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
public class PermissionUtils {
+ private static final String LOG_TAG = PermissionUtils.class.getName();
+ private static final Set<String> LOCATION_PERMISSIONS = new HashSet<String>();
+
+ static {
+ LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+ LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
+ LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
+ }
private static final String ACTION_CHECK_HAS_PERMISSION
= "com.android.cts.permission.action.CHECK_HAS_PERMISSION";
@@ -63,20 +78,25 @@
public static void launchActivityAndRequestPermission(PermissionBroadcastReceiver
receiver, UiDevice device, String permission, int expected,
String packageName, String activityName) throws Exception {
- final String resName;
+ final List<String> resNames = new ArrayList<>();
switch(expected) {
case PERMISSION_DENIED:
- resName = "permission_deny_button";
+ resNames.add("permission_deny_button");
break;
case PERMISSION_GRANTED:
- resName = "permission_allow_button";
+ resNames.add("permission_allow_button");
+ // For the location permission, different buttons may be available.
+ if (LOCATION_PERMISSIONS.contains(permission)) {
+ resNames.add("permission_allow_foreground_only_button");
+ resNames.add("permission_allow_one_time_button");
+ }
break;
default:
throw new IllegalArgumentException("Invalid expected permission");
}
launchActivityWithAction(permission, ACTION_REQUEST_PERMISSION,
packageName, activityName);
- pressPermissionPromptButton(device, resName);
+ pressPermissionPromptButton(device, resNames.toArray(new String[0]));
assertEquals(expected, receiver.waitForBroadcast());
}
@@ -126,17 +146,39 @@
return InstrumentationRegistry.getInstrumentation().getContext();
}
- private static void pressPermissionPromptButton(UiDevice mDevice, String resName) {
- if (resName == null) {
- throw new IllegalArgumentException("resName must not be null");
+ private static void pressPermissionPromptButton(UiDevice mDevice, String[] resNames) {
+ if ((resNames == null) || (resNames.length == 0)) {
+ throw new IllegalArgumentException("resNames must not be null or empty");
}
- BySelector selector = By
- .clazz(android.widget.Button.class.getName())
- .res("com.android.packageinstaller", resName);
- mDevice.wait(Until.hasObject(selector), 5000);
- UiObject2 button = mDevice.findObject(selector);
- assertNotNull("Couldn't find button with resource id: " + resName, button);
- button.click();
+ // The dialog was moved from the packageinstaller to the permissioncontroller.
+ // Search in multiple packages so the test is not affixed to a particular package.
+ String[] possiblePackages = new String[]{
+ "com.android.permissioncontroller.permission.ui",
+ "com.android.packageinstaller",
+ "com.android.permissioncontroller"};
+
+ boolean foundButton = false;
+ for (String resName : resNames) {
+ for (String possiblePkg : possiblePackages) {
+ BySelector selector = By
+ .clazz(android.widget.Button.class.getName())
+ .res(possiblePkg, resName);
+ mDevice.wait(Until.hasObject(selector), 5000);
+ UiObject2 button = mDevice.findObject(selector);
+ Log.d(LOG_TAG, String.format("Resource %s in Package %s found? %b", resName,
+ possiblePkg, button != null));
+ if (button != null) {
+ foundButton = true;
+ button.click();
+ break;
+ }
+ }
+ if (foundButton) {
+ break;
+ }
+ }
+
+ assertTrue("Couldn't find any button", foundButton);
}
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
index 8531f98..ce1bd24 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
@@ -83,9 +83,6 @@
}
}
- // TODO(b/174158829): failing, will need to fix DPMS (it should only allow DO to be set when
- // there is 2 users: system user and current user
- @TemporaryIgnoreOnHeadlessSystemUserMode
@Test
public void testCannotSetDeviceOwnerWhenSecondaryUserPresent() throws Exception {
assumeSupportsMultiUser();
@@ -102,32 +99,26 @@
}
}
- // TODO(b/174158829): this test is currently passing on headless system mode (even without
- // fixing the user id), but it might need to be disabled for that mode, as the headless system
- // user cannot have accounts anyways
+ // TODO(b/174158829): this test is currently failing on headless system mode
@TemporaryIgnoreOnHeadlessSystemUserMode
@FlakyTest
@Test
public void testCannotSetDeviceOwnerWhenAccountPresent() throws Exception {
installAppAsUser(ACCOUNT_MANAGEMENT_APK, mPrimaryUserId);
- installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
+ installAppAsUser(DEVICE_OWNER_APK, mDeviceOwnerUserId);
try {
runDeviceTestsAsUser(ACCOUNT_MANAGEMENT_PKG, ".AccountUtilsTest",
"testAddAccountExplicitly", mPrimaryUserId);
- assertFalse(setDeviceOwner(DEVICE_OWNER_ADMIN_COMPONENT, mPrimaryUserId,
+ assertFalse(setDeviceOwner(DEVICE_OWNER_ADMIN_COMPONENT, mDeviceOwnerUserId,
/*expectFailure*/ true));
} finally {
// make sure we clean up in case we succeeded in setting the device owner
- removeAdmin(DEVICE_OWNER_ADMIN_COMPONENT, mPrimaryUserId);
+ removeAdmin(DEVICE_OWNER_ADMIN_COMPONENT, mDeviceOwnerUserId);
runDeviceTestsAsUser(ACCOUNT_MANAGEMENT_PKG, ".AccountUtilsTest",
"testRemoveAccountExplicitly", mPrimaryUserId);
}
}
- // TODO(b/174158829): failing, most likely due to an issue on DPMS itself:
- //E DevicePolicyManager: In headless system user mode, device owner can only be set on headless
- //system user.
- @TemporaryIgnoreOnHeadlessSystemUserMode
@Test
public void testIsProvisioningAllowed() throws Exception {
// Must install the apk since the test runs in the DO apk.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 874d195..a5b19c2 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -430,6 +430,24 @@
".PermissionsTest", "testPermissionGrantState_developmentPermission");
}
+ @Test
+ public void testGrantOfSensorsRelatedPermissions() throws Exception {
+ installAppPermissionAppAsUser();
+ executeDeviceTestMethod(".PermissionsTest", "testSensorsRelatedPermissionsCannotBeGranted");
+ }
+
+ @Test public void testDenyOfSensorsRelatedPermissions() throws Exception {
+ installAppPermissionAppAsUser();
+ executeDeviceTestMethod(".PermissionsTest", "testSensorsRelatedPermissionsCanBeDenied");
+ }
+
+ @Test
+ public void testSensorsRelatedPermissionsNotGrantedViaPolicy() throws Exception {
+ installAppPermissionAppAsUser();
+ executeDeviceTestMethod(".PermissionsTest",
+ "testSensorsRelatedPermissionsNotGrantedViaPolicy");
+ }
+
/**
* Require a device for tests that use the network stack. Headless Androids running in
* data centres might need their network rules un-tampered-with in order to keep the ADB / VNC
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 54d90fe..3f93c43 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -22,6 +22,7 @@
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.platform.test.annotations.FlakyTest;
import android.platform.test.annotations.LargeTest;
@@ -135,8 +136,7 @@
String.valueOf(Long.MAX_VALUE));
// The next createAndManageUser should return USER_OPERATION_ERROR_LOW_STORAGE.
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_LowStorage");
+ executeCreateAndManageUserTest("testCreateAndManageUser_LowStorage");
} finally {
getDevice().executeShellCommand(
"settings delete global sys_storage_threshold_percentage");
@@ -152,12 +152,10 @@
int maxUsers = getDevice().getMaxNumberOfUsersSupported();
// Primary user is already there, so we can create up to maxUsers -1.
for (int i = 0; i < maxUsers - 1; i++) {
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser");
+ executeCreateAndManageUserTest("testCreateAndManageUser");
}
// The next createAndManageUser should return USER_OPERATION_ERROR_MAX_USERS.
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_MaxUsers");
+ executeCreateAndManageUserTest("testCreateAndManageUser_MaxUsers");
}
/**
@@ -168,8 +166,7 @@
public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
assumeCanCreateOneManagedUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_GetSecondaryUsers");
+ executeCreateAndManageUserTest("testCreateAndManageUser_GetSecondaryUsers");
}
/**
@@ -182,8 +179,7 @@
public void testCreateAndManageUser_SwitchUser() throws Exception {
assumeCanStartNewUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_SwitchUser");
+ executeCreateAndManageUserTest("testCreateAndManageUser_SwitchUser");
}
/**
@@ -195,8 +191,7 @@
public void testCreateAndManageUser_CannotStopCurrentUser() throws Exception {
assumeCanStartNewUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_CannotStopCurrentUser");
+ executeCreateAndManageUserTest("testCreateAndManageUser_CannotStopCurrentUser");
}
/**
@@ -208,8 +203,7 @@
public void testCreateAndManageUser_StartInBackground() throws Exception {
assumeCanStartNewUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_StartInBackground");
+ executeCreateAndManageUserTest("testCreateAndManageUser_StartInBackground");
}
/**
@@ -227,17 +221,16 @@
// Primary user is already running, so we can create and start up to minimum of above - 1.
int usersToCreateAndStart = Math.min(maxUsers, maxRunningUsers) - 1;
for (int i = 0; i < usersToCreateAndStart; i++) {
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_StartInBackground");
+ executeCreateAndManageUserTest("testCreateAndManageUser_StartInBackground");
}
if (maxUsers > maxRunningUsers) {
// The next startUserInBackground should return USER_OPERATION_ERROR_MAX_RUNNING_USERS.
- executeDeviceTestMethod(".CreateAndManageUserTest",
+ executeCreateAndManageUserTest(
"testCreateAndManageUser_StartInBackground_MaxRunningUsers");
} else {
// The next createAndManageUser should return USER_OPERATION_ERROR_MAX_USERS.
- executeDeviceTestMethod(".CreateAndManageUserTest", "testCreateAndManageUser_MaxUsers");
+ executeCreateAndManageUserTest("testCreateAndManageUser_MaxUsers");
}
}
@@ -250,8 +243,7 @@
public void testCreateAndManageUser_StopUser() throws Exception {
assumeCanStartNewUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_StopUser");
+ executeCreateAndManageUserTest("testCreateAndManageUser_StopUser");
assertNewUserStopped();
}
@@ -264,7 +256,7 @@
public void testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser() throws Exception {
assumeCanStartNewUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
+ executeCreateAndManageUserTest(
"testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser");
assertEquals(0, getUsersCreatedByTests().size());
}
@@ -278,8 +270,7 @@
public void testCreateAndManageUser_LogoutUser() throws Exception {
assumeCanStartNewUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_LogoutUser");
+ executeCreateAndManageUserTest("testCreateAndManageUser_LogoutUser");
assertNewUserStopped();
}
@@ -292,8 +283,7 @@
public void testCreateAndManageUser_Affiliated() throws Exception {
assumeCanStartNewUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_Affiliated");
+ executeCreateAndManageUserTest("testCreateAndManageUser_Affiliated");
}
/**
@@ -305,8 +295,7 @@
public void testCreateAndManageUser_Ephemeral() throws Exception {
assumeCanStartNewUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_Ephemeral");
+ executeCreateAndManageUserTest("testCreateAndManageUser_Ephemeral");
List<Integer> newUsers = getUsersCreatedByTests();
assertEquals(1, newUsers.size());
@@ -326,32 +315,28 @@
public void testCreateAndManageUser_LeaveAllSystemApps() throws Exception {
assumeCanStartNewUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_LeaveAllSystemApps");
+ executeCreateAndManageUserTest("testCreateAndManageUser_LeaveAllSystemApps");
}
@Test
public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
assumeCanCreateOneManagedUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_SkipSetupWizard");
+ executeCreateAndManageUserTest("testCreateAndManageUser_SkipSetupWizard");
}
@Test
public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
assumeCanCreateOneManagedUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_AddRestrictionSet");
+ executeCreateAndManageUserTest("testCreateAndManageUser_AddRestrictionSet");
}
@Test
public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
assumeCanCreateOneManagedUser();
- executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_RemoveRestrictionSet");
+ executeCreateAndManageUserTest("testCreateAndManageUser_RemoveRestrictionSet");
}
@FlakyTest(bugId = 126955083)
@@ -359,11 +344,10 @@
public void testUserAddedOrRemovedBroadcasts() throws Exception {
assumeCanCreateOneManagedUser();
- executeDeviceTestMethod(".CreateAndManageUserTest", "testUserAddedOrRemovedBroadcasts");
+ executeCreateAndManageUserTest("testUserAddedOrRemovedBroadcasts");
}
@Test
- @TemporaryIgnoreOnHeadlessSystemUserMode
public void testUserSession() throws Exception {
executeDeviceOwnerTest("UserSessionTest");
}
@@ -852,10 +836,86 @@
}
@Test
- public void testDevicePolicySafetyCheckerIntegration() throws Exception {
+ public void testDevicePolicySafetyCheckerIntegration_allOperations() throws Exception {
executeDeviceTestMethod(".DevicePolicySafetyCheckerIntegrationTest", "testAllOperations");
}
+ @Test
+ public void testDevicePolicySafetyCheckerIntegration_isSafeOperation() throws Exception {
+ executeDeviceTestMethod(".DevicePolicySafetyCheckerIntegrationTest", "testIsSafeOperation");
+ }
+
+ @Test
+ public void testDevicePolicySafetyCheckerIntegration_unsafeStateException() throws Exception {
+ executeDeviceTestMethod(".DevicePolicySafetyCheckerIntegrationTest",
+ "testUnsafeStateException");
+ }
+
+ @Test
+ public void testDevicePolicySafetyCheckerIntegration_onOperationSafetyStateChanged()
+ throws Exception {
+ executeDeviceTestMethod(".DevicePolicySafetyCheckerIntegrationTest",
+ "testOnOperationSafetyStateChanged");
+ }
+
+ @Test
+ public void testListForegroundAffiliatedUsers_notDeviceOwner() throws Exception {
+ if (!removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId)) {
+ fail("Failed to remove device owner for user " + mDeviceOwnerUserId);
+ }
+
+ executeDeviceTestMethod(".PreDeviceOwnerTest",
+ "testListForegroundAffiliatedUsers_notDeviceOwner");
+ }
+
+ @Test
+ public void testListForegroundAffiliatedUsers_onlyForegroundUser() throws Exception {
+ executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+ "testListForegroundAffiliatedUsers_onlyForegroundUser");
+ }
+
+ // TODO(b/132260693): createAffiliatedSecondaryUser() is failing on headless user
+ @TemporaryIgnoreOnHeadlessSystemUserMode
+ @Test
+ public void testListForegroundAffiliatedUsers_extraUser() throws Exception {
+ assumeCanCreateAdditionalUsers(1);
+ createAffiliatedSecondaryUser();
+
+ executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+ "testListForegroundAffiliatedUsers_onlyForegroundUser");
+ }
+
+ @Test
+ public void testListForegroundAffiliatedUsers_notAffiliated() throws Exception {
+ assumeCanCreateAdditionalUsers(1);
+ int userId = createUser();
+ switchUser(userId);
+
+ executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+ "testListForegroundAffiliatedUsers_empty");
+ }
+
+ // TODO(b/132260693): createAffiliatedSecondaryUser() is failing on headless user
+ @TemporaryIgnoreOnHeadlessSystemUserMode
+ @Test
+ public void testListForegroundAffiliatedUsers_affiliated() throws Exception {
+ assumeCanCreateAdditionalUsers(1);
+ int userId = createAffiliatedSecondaryUser();
+ switchUser(userId);
+
+ executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+ "testListForegroundAffiliatedUsers_onlyForegroundUser");
+ }
+
+ @TemporaryIgnoreOnHeadlessSystemUserMode
+ @Test
+ public void testWifiNetworkConfigurationWithoutFineLocationPermission() throws Exception {
+ getDevice().executeShellCommand(String.format(
+ "pm revoke %s android.permission.ACCESS_FINE_LOCATION", DEVICE_OWNER_PKG));
+
+ executeDeviceOwnerTest("WifiNetworkConfigurationWithoutFineLocationPermissionTest");
+ }
+
private int createAffiliatedSecondaryUser() throws Exception {
final int userId = createUser();
installAppAsUser(INTENT_RECEIVER_APK, userId);
@@ -877,6 +937,10 @@
/* deviceOwnerUserId */ mPrimaryUserId, params);
}
+ private void executeCreateAndManageUserTest(String testMethod) throws Exception {
+ executeDeviceTestMethod(".CreateAndManageUserTest", testMethod);
+ }
+
private void assertNewUserStopped() throws Exception {
List<Integer> newUsers = getUsersCreatedByTests();
assertEquals(1, newUsers.size());
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index c6d318a..03e18b4 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -105,6 +105,7 @@
@LargeTest
@Test
+ @Ignore
public void testAppLinks_verificationStatus() throws Exception {
// Disable all pre-existing browsers in the managed profile so they don't interfere with
// intents resolution.
@@ -140,6 +141,7 @@
@LargeTest
@Test
+ @Ignore
public void testAppLinks_enabledStatus() throws Exception {
// Disable all pre-existing browsers in the managed profile so they don't interfere with
// intents resolution.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 5f07a53..52985cb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -44,6 +44,7 @@
public class MixedDeviceOwnerTest extends DeviceAndProfileOwnerTest {
private static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
+ private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
private static final String ARG_SECURITY_LOGGING_BATCH_NUMBER = "batchNumber";
private static final int SECURITY_EVENTS_BATCH_SIZE = 100;
@@ -178,15 +179,18 @@
.setAdminPackageName(DELEGATE_APP_PKG)
.setBoolean(true)
.setInt(1)
+ .setStrings(LOG_TAG_DEVICE_OWNER)
.build(),
new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_NETWORK_LOGS_VALUE)
.setAdminPackageName(DELEGATE_APP_PKG)
.setBoolean(true)
+ .setStrings(LOG_TAG_DEVICE_OWNER)
.build(),
new DevicePolicyEventWrapper.Builder(EventId.SET_NETWORK_LOGGING_ENABLED_VALUE)
.setAdminPackageName(DELEGATE_APP_PKG)
.setBoolean(true)
.setInt(0)
+ .setStrings(LOG_TAG_DEVICE_OWNER)
.build(),
};
result.put(".NetworkLoggingDelegateTest", expectedMetrics);
@@ -367,6 +371,17 @@
"testAdminCanGrantSensorsPermissions");
}
+ @Override
+ @Test
+ public void testGrantOfSensorsRelatedPermissions() throws Exception {
+ // Skip for now, re-enable when the code path sets DO as able to grant permissions.
+ }
+
+ @Override
+ @Test
+ public void testSensorsRelatedPermissionsNotGrantedViaPolicy() throws Exception {
+ // Skip for now, re-enable when the code path sets DO as able to grant permissions.
+ }
private void configureNotificationListener() throws DeviceNotAvailableException {
getDevice().executeShellCommand("cmd notification allow_listener "
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 1e3e73c..4924f00 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -134,6 +134,11 @@
// and profile owners on the primary user.
}
+ @Test
+ public void testSetGetNetworkSlicingStatus() throws Exception {
+ executeDeviceTestMethod(".NetworkSlicingStatusTest", "testGetSetNetworkSlicingStatus");
+ }
+
/** VPN tests don't require physical device for managed profile, thus overriding. */
@FlakyTest
@Override
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index 1321697..a05eab1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -48,6 +48,7 @@
private static final String CERT_INSTALLER_APK = DeviceAndProfileOwnerTest.CERT_INSTALLER_APK;
private static final String DELEGATE_APP_PKG = DeviceAndProfileOwnerTest.DELEGATE_APP_PKG;
private static final String DELEGATE_APP_APK = DeviceAndProfileOwnerTest.DELEGATE_APP_APK;
+ private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
private static final String ADMIN_RECEIVER_TEST_CLASS =
DeviceAndProfileOwnerTest.ADMIN_RECEIVER_TEST_CLASS;
@@ -662,6 +663,30 @@
}
}
+ @Test
+ public void testNetworkLoggingLogged() throws Exception {
+ installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+ assertMetricsLogged(getDevice(), () -> {
+ testNetworkLoggingOnWorkProfile(DEVICE_ADMIN_PKG, ".NetworkLoggingTest");
+ }, new DevicePolicyEventWrapper.Builder(EventId.SET_NETWORK_LOGGING_ENABLED_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .setBoolean(false)
+ .setInt(1)
+ .setStrings(LOG_TAG_PROFILE_OWNER)
+ .build(),
+ new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_NETWORK_LOGS_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .setBoolean(false)
+ .setStrings(LOG_TAG_PROFILE_OWNER)
+ .build(),
+ new DevicePolicyEventWrapper.Builder(EventId.SET_NETWORK_LOGGING_ENABLED_VALUE)
+ .setAdminPackageName(DEVICE_ADMIN_PKG)
+ .setBoolean(false)
+ .setInt(0)
+ .setStrings(LOG_TAG_PROFILE_OWNER)
+ .build());
+ }
+
private void toggleQuietMode(boolean quietModeEnable) throws Exception {
final String str;
// TV launcher uses intent filter priority to prevent 3p launchers replacing it
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
index aedcabc..d4c3c14 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -73,8 +73,24 @@
}
@Test
- public void testDevicePolicySafetyCheckerIntegration() throws Exception {
- executeProfileOwnerTest("DevicePolicySafetyCheckerIntegrationTest");
+ public void testDevicePolicySafetyCheckerIntegration_allOperations() throws Exception {
+ executeDevicePolicySafetyCheckerIntegrationTest("testAllOperations");
+ }
+
+ @Test
+ public void testDevicePolicySafetyCheckerIntegration_isSafeOperation() throws Exception {
+ executeDevicePolicySafetyCheckerIntegrationTest("testIsSafeOperation");
+ }
+
+ @Test
+ public void testDevicePolicySafetyCheckerIntegration_unsafeStateException() throws Exception {
+ executeDevicePolicySafetyCheckerIntegrationTest("testUnsafeStateException");
+ }
+
+ @Test
+ public void testDevicePolicySafetyCheckerIntegration_onOperationSafetyStateChanged()
+ throws Exception {
+ executeDevicePolicySafetyCheckerIntegrationTest("testOnOperationSafetyStateChanged");
}
@Override
@@ -95,4 +111,9 @@
throws Exception {
runDeviceTestsAsUser(PROFILE_OWNER_PKG, className, testName, mUserId);
}
+
+ private void executeDevicePolicySafetyCheckerIntegrationTest(String testName) throws Exception {
+ executeProfileOwnerTestMethod(
+ PROFILE_OWNER_PKG + "." + "DevicePolicySafetyCheckerIntegrationTest", testName);
+ }
}
diff --git a/hostsidetests/dexmetadata/host/README.md b/hostsidetests/dexmetadata/host/README.md
new file mode 100644
index 0000000..b1b3a95
--- /dev/null
+++ b/hostsidetests/dexmetadata/host/README.md
@@ -0,0 +1,30 @@
+Fs-verity keys
+==============
+All AOSP compatible devices ship with the Google-managed fs-verity certificate
+(located at build/make/target/product/security/fsverity-release.x509.der). The
+public key can verify the signature prebuilt of .dm.fsv\_sig in res/.
+
+Modifying a .dm file requires to regenerate the signature with some debug key.
+To use the debug key, you can run the following commands once per boot.
+
+```
+KEY_DIR=$ANDROID_BUILD_TOP/cts/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata
+
+adb root
+adb shell 'mini-keyctl padd asymmetric fsv-play .fs-verity' < $KEY_DIR/fsverity-debug.x509.der
+```
+
+Alternatively, copy the .der file to /{system, product}/etc/security/fsverity.
+The key will be located upon reboot.
+
+How to modify the signed .dm
+============================
+The easiet way is to re-sign and replace the signature in place. For example,
+
+```
+m fsverity
+
+fsverity sign CtsDexMetadataSplitApp.dm CtsDexMetadataSplitApp.dm.fsv_sig \
+ --key="$KEY_DIR/fsverity-debug-key.pem" \
+ --cert="$KEY_DIR/fsverity-debug.x509.pem"
+```
diff --git a/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitApp.dm.fsv_sig b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitApp.dm.fsv_sig
new file mode 100644
index 0000000..ba049b5
--- /dev/null
+++ b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitApp.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureA.dm.fsv_sig b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureA.dm.fsv_sig
new file mode 100644
index 0000000..2f56cb0
--- /dev/null
+++ b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureA.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureAWithVdex.dm.fsv_sig b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureAWithVdex.dm.fsv_sig
new file mode 100644
index 0000000..4957651
--- /dev/null
+++ b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppFeatureAWithVdex.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppWithVdex.dm.fsv_sig b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppWithVdex.dm.fsv_sig
new file mode 100644
index 0000000..fc9f808
--- /dev/null
+++ b/hostsidetests/dexmetadata/host/res/CtsDexMetadataSplitAppWithVdex.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/BaseInstallMultiple.java b/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/BaseInstallMultiple.java
index eb9498e..7f81e06 100644
--- a/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/BaseInstallMultiple.java
+++ b/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/BaseInstallMultiple.java
@@ -57,8 +57,17 @@
return (T) this;
}
- T addDm(File dma) {
+ T addDm(File dma, File sig) {
mFilesToInstall.add(dma);
+ if (sig != null) {
+ mFilesToInstall.add(sig);
+ }
+ return (T) this;
+ }
+
+ T inheritFrom(String packageName) {
+ addArg("-r");
+ addArg("-p " + packageName);
return (T) this;
}
diff --git a/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/InstallDexMetadataHostTest.java b/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/InstallDexMetadataHostTest.java
index ad6aef8..7c11420 100644
--- a/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/InstallDexMetadataHostTest.java
+++ b/hostsidetests/dexmetadata/host/src/com/android/cts/dexmetadata/InstallDexMetadataHostTest.java
@@ -18,10 +18,13 @@
import static org.junit.Assert.assertArrayEquals;
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 com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.FileUtil;
@@ -68,23 +71,32 @@
private static final String DM_FEATURE_A_WITH_VDEX
= "CtsDexMetadataSplitAppFeatureAWithVdex.dm";
+ private static final String APK_VERITY_STANDARD_MODE = "2";
+ private static final String FSV_SIG_SUFFIX = ".fsv_sig";
+
private File mTmpDir;
private File mApkBaseFile = null;
private File mApkFeatureAFile = null;
private File mApkBaseFileWithVdex = null;
private File mApkFeatureAFileWithVdex = null;
private File mDmBaseFile = null;
+ private File mDmBaseFsvSigFile = null;
private File mDmFeatureAFile = null;
+ private File mDmFeatureAFsvSigFile = null;
private File mDmBaseFileWithVdex = null;
+ private File mDmBaseFileWithVdexFsvSig = null;
private File mDmFeatureAFileWithVdex = null;
+ private File mDmFeatureAFileWithVdexFsvSig = null;
private boolean mShouldRunTests;
+ private boolean mFsVerityRequiredForDm;
/**
* Setup the test.
*/
@Before
public void setUp() throws Exception {
- getDevice().uninstallPackage(INSTALL_PACKAGE);
+ ITestDevice device = getDevice();
+ device.uninstallPackage(INSTALL_PACKAGE);
mShouldRunTests = ApiLevelUtil.isAtLeast(getDevice(), 28)
|| ApiLevelUtil.isAtLeast(getDevice(), "P")
|| ApiLevelUtil.codenameEquals(getDevice(), "P");
@@ -92,15 +104,27 @@
Assume.assumeTrue("Skip DexMetadata tests on releases before P.", mShouldRunTests);
if (mShouldRunTests) {
+ boolean fsVeritySupported = device.getLaunchApiLevel() >= 30
+ || APK_VERITY_STANDARD_MODE.equals(device.getProperty("ro.apk_verity.mode"));
+ boolean fsVerityRequired = "true".equals(
+ device.getProperty("pm.dexopt.dm.require_fsverity"));
+ mFsVerityRequiredForDm = fsVeritySupported && fsVerityRequired;
+
mTmpDir = FileUtil.createTempDir("InstallDexMetadataHostTest");
mApkBaseFile = extractResource(APK_BASE, mTmpDir);
mApkFeatureAFile = extractResource(APK_FEATURE_A, mTmpDir);
mApkBaseFileWithVdex = extractResource(APK_BASE_WITH_VDEX, mTmpDir);
mApkFeatureAFileWithVdex = extractResource(APK_FEATURE_A_WITH_VDEX, mTmpDir);
mDmBaseFile = extractResource(DM_BASE, mTmpDir);
+ mDmBaseFsvSigFile = extractResource(DM_BASE + FSV_SIG_SUFFIX , mTmpDir);
mDmFeatureAFile = extractResource(DM_FEATURE_A, mTmpDir);
+ mDmFeatureAFsvSigFile = extractResource(DM_FEATURE_A + FSV_SIG_SUFFIX, mTmpDir);
mDmBaseFileWithVdex = extractResource(DM_BASE_WITH_VDEX, mTmpDir);
+ mDmBaseFileWithVdexFsvSig = extractResource(
+ DM_BASE_WITH_VDEX + FSV_SIG_SUFFIX, mTmpDir);
mDmFeatureAFileWithVdex = extractResource(DM_FEATURE_A_WITH_VDEX, mTmpDir);
+ mDmFeatureAFileWithVdexFsvSig = extractResource(
+ DM_FEATURE_A_WITH_VDEX + FSV_SIG_SUFFIX, mTmpDir);
}
}
@@ -118,7 +142,7 @@
*/
@Test
public void testInstallDmForBase() throws Exception {
- new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile).run();
+ new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile).run();
assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBase"));
@@ -129,8 +153,8 @@
*/
@Test
public void testInstallDmForBaseAndSplit() throws Exception {
- new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile)
- .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile).run();
+ new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile)
+ .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile, mDmFeatureAFsvSigFile).run();
assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBaseAndSplit"));
@@ -141,7 +165,7 @@
*/
@Test
public void testInstallDmForBaseButNoSplit() throws Exception {
- new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile)
+ new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile)
.addApk(mApkFeatureAFile).run();
assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
@@ -154,7 +178,7 @@
@Test
public void testInstallDmForSplitButNoBase() throws Exception {
new InstallMultiple().addApk(mApkBaseFile)
- .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile).run();
+ .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile, mDmFeatureAFsvSigFile).run();
assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForSplitButNoBase"));
@@ -165,8 +189,8 @@
*/
@Test
public void testUpdateDm() throws Exception {
- new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile)
- .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile).run();
+ new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile)
+ .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile, mDmFeatureAFsvSigFile).run();
assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBaseAndSplit"));
@@ -180,11 +204,12 @@
// Add only a split .dm file during update.
new InstallMultiple().addArg("-r").addApk(mApkBaseFile)
- .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile).run();
+ .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile, mDmFeatureAFsvSigFile).run();
assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForSplitButNoBase"));
}
+
/**
* Verify .dm installation for base but not for splits and with a .dm file that doesn't match
* an apk.
@@ -194,8 +219,12 @@
File nonMatchingDm = new File(mDmFeatureAFile.getAbsoluteFile().getAbsolutePath()
.replace(".dm", ".not.there.dm"));
FileUtil.copyFile(mDmFeatureAFile, nonMatchingDm);
- new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile)
- .addApk(mApkFeatureAFile).addDm(nonMatchingDm).run();
+ File nonMatchingDmFsvSig = new File(mDmFeatureAFsvSigFile.getAbsoluteFile()
+ .getAbsolutePath()
+ .replace(".dm" + FSV_SIG_SUFFIX, ".not.there.dm" + FSV_SIG_SUFFIX));
+ FileUtil.copyFile(mDmFeatureAFsvSigFile, nonMatchingDmFsvSig);
+ new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile)
+ .addApk(mApkFeatureAFile).addDm(nonMatchingDm, nonMatchingDmFsvSig).run();
assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBaseButNoSplit"));
@@ -228,7 +257,7 @@
assumeProfilesAreEnabled();
// Install the app.
- new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile).run();
+ new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile).run();
// Take a snapshot of the installed profile.
String snapshotCmd = "cmd package snapshot-profile " + INSTALL_PACKAGE;
@@ -248,7 +277,8 @@
*/
@Test
public void testInstallDmForBaseWithVdex() throws Exception {
- new InstallMultiple().addApk(mApkBaseFileWithVdex).addDm(mDmBaseFileWithVdex).run();
+ new InstallMultiple().addApk(mApkBaseFileWithVdex)
+ .addDm(mDmBaseFileWithVdex, mDmBaseFileWithVdexFsvSig).run();
assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBase"));
@@ -259,13 +289,61 @@
*/
@Test
public void testInstallDmForBaseAndSplitWithVdex() throws Exception {
- new InstallMultiple().addApk(mApkBaseFileWithVdex).addDm(mDmBaseFileWithVdex)
- .addApk(mApkFeatureAFileWithVdex).addDm(mDmFeatureAFileWithVdex).run();
+ new InstallMultiple().addApk(mApkBaseFileWithVdex)
+ .addDm(mDmBaseFileWithVdex, mDmBaseFileWithVdexFsvSig)
+ .addApk(mApkFeatureAFileWithVdex)
+ .addDm(mDmFeatureAFileWithVdex, mDmFeatureAFileWithVdexFsvSig).run();
assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBaseAndSplit"));
}
+ /** Verify .dm installation without .fsv_sig for base. */
+ @Test
+ public void testInstallDmFailedWithoutFsvSigForBase() throws Exception {
+ InstallMultiple installer = new InstallMultiple().addApk(mApkBaseFile)
+ .addDm(mDmBaseFile, null);
+ if (mFsVerityRequiredForDm) {
+ installer.runExpectingFailure();
+ assertNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+ } else {
+ installer.run();
+ assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+ assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBase"));
+ }
+ }
+
+ /** Verify .dm installation without .fsv_sig for split. */
+ @Test
+ public void testInstallDmWithoutFsvSigForSplit() throws Exception {
+ InstallMultiple installer = new InstallMultiple()
+ .addApk(mApkBaseFile)
+ .addDm(mDmBaseFile, mDmBaseFsvSigFile)
+ .addApk(mApkFeatureAFile)
+ .addDm(mDmFeatureAFile, null);
+ if (mFsVerityRequiredForDm) {
+ installer.runExpectingFailure();
+ assertNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+ } else {
+ installer.run();
+ assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+ assertTrue(runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testDmForBaseAndSplit"));
+ }
+ }
+
+ /** Verify .dm installation without .fsv_sig for split-only install. */
+ @Test
+ public void testInstallDmWithoutFsvSigForSplitOnlyInstall() throws Exception {
+ new InstallMultiple().addApk(mApkBaseFile).addDm(mDmBaseFile, mDmBaseFsvSigFile).run();
+ assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+
+ new InstallMultiple()
+ .inheritFrom(TEST_PACKAGE)
+ .addApk(mApkFeatureAFile).addDm(mDmFeatureAFile, null)
+ .runExpectingFailure();
+ assertNotNull(getDevice().getAppPackageInfo(INSTALL_PACKAGE));
+ }
+
/** Verify that the use of profiles is enabled on the device. */
private void assumeProfilesAreEnabled() throws Exception {
String useProfiles = getDevice().executeShellCommand(
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
index 7c966dd0..2626bb3 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
@@ -188,7 +188,8 @@
}
private static void setCecVersion(ITestDevice device, int cecVersion) throws Exception {
- device.executeShellCommand("settings put global hdmi_cec_version " + cecVersion);
+ device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_version " +
+ cecVersion);
TimeUnit.SECONDS.sleep(HdmiCecConstants.TIMEOUT_CEC_REINIT_SECONDS);
}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java
index 04a3178..e6ffaf9 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java
@@ -49,7 +49,7 @@
.around(hdmiCecClient);
public HdmiCecRemoteControlPassThroughTest() {
- super(LogicalAddress.TV, "-t", "r", "-t", "p");
+ super(LogicalAddress.TV, "-t", "r", "-t", "p", "-t", "t");
mapRemoteControlKeys();
}
@@ -100,6 +100,19 @@
validateKeyeventToUserControlPress(LogicalAddress.PLAYBACK_1);
}
+ /**
+ * Test 11.1.13-3
+ *
+ * <p>Tests that the DUT sends the appropriate messages for remote control pass through to a
+ * Tuner Device.
+ */
+ @Test
+ public void cect_11_1_13_3_RemoteControlMessagesToTuner() throws Exception {
+ hdmiCecClient.broadcastActiveSource(
+ LogicalAddress.TUNER_1, hdmiCecClient.getPhysicalAddress());
+ validateKeyeventToUserControlPress(LogicalAddress.TUNER_1);
+ }
+
private void mapRemoteControlKeys() {
remoteControlKeys.put("DPAD_UP", HdmiCecConstants.CEC_CONTROL_UP);
remoteControlKeys.put("DPAD_DOWN", HdmiCecConstants.CEC_CONTROL_DOWN);
diff --git a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
deleted file mode 100644
index a5cb9d7..0000000
--- a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
+++ /dev/null
@@ -1,219 +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.server.cts;
-
-import com.android.server.alarm.AlarmClockMetadataProto;
-import com.android.server.alarm.AlarmManagerServiceDumpProto;
-import com.android.server.alarm.AlarmProto;
-import com.android.server.alarm.BatchProto;
-import com.android.server.alarm.BroadcastStatsProto;
-import com.android.server.alarm.ConstantsProto;
-import com.android.server.alarm.FilterStatsProto;
-import com.android.server.AppStateTrackerProto;
-import com.android.server.AppStateTrackerProto.RunAnyInBackgroundRestrictedPackages;
-import com.android.server.alarm.IdleDispatchEntryProto;
-import com.android.server.alarm.InFlightProto;
-import com.android.server.alarm.WakeupEventProto;
-import java.util.List;
-
-/**
- * Test to check that the alarm manager service properly outputs its dump state.
- */
-public class AlarmManagerIncidentTest extends ProtoDumpTestCase {
- public void testAlarmManagerServiceDump() throws Exception {
- final AlarmManagerServiceDumpProto dump =
- getDump(AlarmManagerServiceDumpProto.parser(), "dumpsys alarm --proto");
-
- verifyAlarmManagerServiceDumpProto(dump, PRIVACY_NONE);
- }
-
- static void verifyAlarmManagerServiceDumpProto(AlarmManagerServiceDumpProto dump, final int filterLevel) throws Exception {
- // Times should be positive.
- assertTrue(0 < dump.getCurrentTime());
- assertTrue(0 < dump.getElapsedRealtime());
- // Can be 0 if the time hasn't been changed yet.
- assertTrue(0 <= dump.getLastTimeChangeClockTime());
- assertTrue(0 <= dump.getLastTimeChangeRealtime());
-
- // ConstantsProto
- ConstantsProto settings = dump.getSettings();
- assertTrue(0 < settings.getMinFuturityDurationMs());
- assertTrue(0 < settings.getMinIntervalDurationMs());
- assertTrue(0 < settings.getListenerTimeoutDurationMs());
- assertTrue(0 < settings.getAllowWhileIdleShortDurationMs());
- assertTrue(0 < settings.getAllowWhileIdleLongDurationMs());
- assertTrue(0 < settings.getAllowWhileIdleWhitelistDurationMs());
-
- // AppStateTrackerProto
- AppStateTrackerProto appStateTracker = dump.getAppStateTracker();
- for (int uid : appStateTracker.getForegroundUidsList()) {
- // 0 is technically a valid UID.
- assertTrue(0 <= uid);
- }
- for (int aid : appStateTracker.getPowerSaveExemptAppIdsList()) {
- assertTrue(0 <= aid);
- }
- for (int aid : appStateTracker.getTempPowerSaveExemptAppIdsList()) {
- assertTrue(0 <= aid);
- }
- for (RunAnyInBackgroundRestrictedPackages r : appStateTracker.getRunAnyInBackgroundRestrictedPackagesList()) {
- assertTrue(0 <= r.getUid());
- }
-
- if (!dump.getIsInteractive()) {
- // These are only valid if is_interactive is false.
- assertTrue(0 < dump.getTimeSinceNonInteractiveMs());
- assertTrue(0 < dump.getMaxWakeupDelayMs());
- assertTrue(0 < dump.getTimeSinceLastDispatchMs());
- // time_until_next_non_wakeup_delivery_ms could be negative if the delivery time is in the past.
- }
-
- assertTrue(0 < dump.getTimeUntilNextWakeupMs());
- assertTrue(0 < dump.getTimeSinceLastWakeupMs());
- assertTrue(0 < dump.getTimeSinceLastWakeupSetMs());
- assertTrue(0 <= dump.getTimeChangeEventCount());
-
- for (int aid : dump.getDeviceIdleUserExemptAppIdsList()) {
- assertTrue(0 <= aid);
- }
-
- // AlarmClockMetadataProto
- for (AlarmClockMetadataProto ac : dump.getNextAlarmClockMetadataList()) {
- assertTrue(0 <= ac.getUser());
- assertTrue(0 < ac.getTriggerTimeMs());
- }
-
- for (BatchProto b : dump.getPendingAlarmBatchesList()) {
- final long start = b.getStartRealtime();
- final long end = b.getEndRealtime();
- assertTrue("Batch start time (" + start+ ") is negative", 0 <= start);
- assertTrue("Batch end time (" + end + ") is negative", 0 <= end);
- assertTrue("Batch start time (" + start + ") is after its end time (" + end + ")",
- start <= end);
- testAlarmProtoList(b.getAlarmsList(), filterLevel);
- }
-
- testAlarmProtoList(dump.getPendingUserBlockedBackgroundAlarmsList(), filterLevel);
-
- testAlarmProto(dump.getPendingIdleUntil(), filterLevel);
-
- testAlarmProtoList(dump.getPendingWhileIdleAlarmsList(), filterLevel);
-
- testAlarmProto(dump.getNextWakeFromIdle(), filterLevel);
-
- testAlarmProtoList(dump.getPastDueNonWakeupAlarmsList(), filterLevel);
-
- assertTrue(0 <= dump.getDelayedAlarmCount());
- assertTrue(0 <= dump.getTotalDelayTimeMs());
- assertTrue(0 <= dump.getMaxDelayDurationMs());
- assertTrue(0 <= dump.getMaxNonInteractiveDurationMs());
-
- assertTrue(0 <= dump.getBroadcastRefCount());
- assertTrue(0 <= dump.getPendingIntentSendCount());
- assertTrue(0 <= dump.getPendingIntentFinishCount());
- assertTrue(0 <= dump.getListenerSendCount());
- assertTrue(0 <= dump.getListenerFinishCount());
-
- for (InFlightProto f : dump.getOutstandingDeliveriesList()) {
- assertTrue(0 <= f.getUid());
- assertTrue(0 < f.getWhenElapsedMs());
- testBroadcastStatsProto(f.getBroadcastStats());
- testFilterStatsProto(f.getFilterStats(), filterLevel);
- if (filterLevel == PRIVACY_AUTO) {
- assertTrue(f.getTag().isEmpty());
- }
- }
-
- for (AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch l : dump.getLastAllowWhileIdleDispatchTimesList()) {
- assertTrue(0 <= l.getUid());
- assertTrue(0 < l.getTimeMs());
- }
-
- for (AlarmManagerServiceDumpProto.TopAlarm ta : dump.getTopAlarmsList()) {
- assertTrue(0 <= ta.getUid());
- testFilterStatsProto(ta.getFilter(), filterLevel);
- }
-
- for (AlarmManagerServiceDumpProto.AlarmStat as : dump.getAlarmStatsList()) {
- testBroadcastStatsProto(as.getBroadcast());
- for (FilterStatsProto f : as.getFiltersList()) {
- testFilterStatsProto(f, filterLevel);
- }
- }
-
- for (IdleDispatchEntryProto id : dump.getAllowWhileIdleDispatchesList()) {
- assertTrue(0 <= id.getUid());
- assertTrue(0 <= id.getEntryCreationRealtime());
- assertTrue(0 <= id.getArgRealtime());
- if (filterLevel == PRIVACY_AUTO) {
- assertTrue(id.getTag().isEmpty());
- }
- }
-
- for (WakeupEventProto we : dump.getRecentWakeupHistoryList()) {
- assertTrue(0 <= we.getUid());
- assertTrue(0 <= we.getWhen());
- }
- }
-
- private static void testAlarmProtoList(List<AlarmProto> alarms, final int filterLevel) throws Exception {
- for (AlarmProto a : alarms) {
- testAlarmProto(a, filterLevel);
- }
- }
-
- private static void testAlarmProto(AlarmProto alarm, final int filterLevel) throws Exception {
- assertNotNull(alarm);
-
- if (filterLevel == PRIVACY_AUTO) {
- assertTrue(alarm.getTag().isEmpty());
- assertTrue(alarm.getListener().isEmpty());
- }
- // alarm.time_until_when_elapsed_ms can be negative if 'when' is in the past.
- assertTrue(0 <= alarm.getWindowLengthMs());
- assertTrue(0 <= alarm.getRepeatIntervalMs());
- assertTrue(0 <= alarm.getCount());
- }
-
- private static void testBroadcastStatsProto(BroadcastStatsProto broadcast) throws Exception {
- assertNotNull(broadcast);
-
- assertTrue(0 <= broadcast.getUid());
- assertTrue(0 <= broadcast.getTotalFlightDurationMs());
- assertTrue(0 <= broadcast.getCount());
- assertTrue(0 <= broadcast.getWakeupCount());
- assertTrue(0 <= broadcast.getStartTimeRealtime());
- // Nesting should be non-negative.
- assertTrue(0 <= broadcast.getNesting());
- }
-
- private static void testFilterStatsProto(FilterStatsProto filter, final int filterLevel) throws Exception {
- assertNotNull(filter);
-
- if (filterLevel == PRIVACY_AUTO) {
- assertTrue(filter.getTag().isEmpty());
- }
- assertTrue(0 <= filter.getLastFlightTimeRealtime());
- assertTrue(0 <= filter.getTotalFlightDurationMs());
- assertTrue(0 <= filter.getCount());
- assertTrue(0 <= filter.getWakeupCount());
- assertTrue(0 <= filter.getStartTimeRealtime());
- // Nesting should be non-negative.
- assertTrue(0 <= filter.getNesting());
- }
-}
-
diff --git a/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
index 33859bf..9304f46 100644
--- a/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
+import android.platform.test.annotations.RequiresDevice;
import android.service.GraphicsStatsHistogramBucketProto;
import android.service.GraphicsStatsJankSummaryProto;
import android.service.GraphicsStatsProto;
@@ -28,6 +29,11 @@
import java.util.Date;
import java.util.List;
+// Although this test does not directly test performance, it does indirectly require consistent
+// performance for the "good" frames. Although pass-through GPU virtual devices should have
+// sufficient performance to pass OK, not all virtual devices do. So restrict this to physical
+// devices.
+@RequiresDevice
public class GraphicsStatsValidationTest extends ProtoDumpTestCase {
private static final String TAG = "GraphicsStatsValidationTest";
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
index ba3a907..b136927 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
@@ -66,8 +66,6 @@
ActivityManagerIncidentTest.verifyActivityManagerServiceDumpProcessesProto(dump.getAmprocesses(), filterLevel);
- AlarmManagerIncidentTest.verifyAlarmManagerServiceDumpProto(dump.getAlarm(), filterLevel);
-
// GraphicsStats is expected to be all AUTOMATIC.
WindowManagerIncidentTest.verifyWindowManagerServiceDumpProto(dump.getWindow(), filterLevel);
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/OWNERS b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/OWNERS
index 464ed7d..fb8017c 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/OWNERS
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/OWNERS
@@ -1 +1 @@
-per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS
+per-file *InlineSuggestion* = file:platform/frameworks/base:/core/java/android/service/autofill/OWNERS
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/OWNERS b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/OWNERS
index 464ed7d..fb8017c 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/OWNERS
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/OWNERS
@@ -1 +1 @@
-per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS
+per-file *InlineSuggestion* = file:platform/frameworks/base:/core/java/android/service/autofill/OWNERS
diff --git a/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java b/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
index 31e64c1..8416e58 100644
--- a/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
+++ b/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
@@ -18,8 +18,12 @@
import android.content.Context;
import android.media.metrics.MediaMetricsManager;
+import android.media.metrics.NetworkEvent;
+import android.media.metrics.PlaybackErrorEvent;
+import android.media.metrics.PlaybackMetrics;
import android.media.metrics.PlaybackSession;
import android.media.metrics.PlaybackStateEvent;
+import android.media.metrics.TrackChangeEvent;
import androidx.test.InstrumentationRegistry;
@@ -39,4 +43,77 @@
.build();
s.reportPlaybackStateEvent(e);
}
+
+ @Test
+ public void testPlaybackErrorEvent() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+ PlaybackSession s = manager.createPlaybackSession();
+ PlaybackErrorEvent e =
+ new PlaybackErrorEvent.Builder()
+ .setTimeSinceCreatedMillis(17630000L)
+ .setErrorCode(PlaybackErrorEvent.ERROR_CODE_RUNTIME)
+ .setSubErrorCode(378)
+ .setException(new Exception("test exception"))
+ .build();
+ s.reportPlaybackErrorEvent(e);
+ }
+
+ @Test
+ public void testTrackChangeEvent_text() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+ PlaybackSession s = manager.createPlaybackSession();
+ TrackChangeEvent e =
+ new TrackChangeEvent.Builder(TrackChangeEvent.TRACK_TYPE_TEXT)
+ .setTimeSinceCreatedMillis(37278L)
+ .setTrackState(TrackChangeEvent.TRACK_STATE_ON)
+ .setTrackChangeReason(TrackChangeEvent.TRACK_CHANGE_REASON_MANUAL)
+ .setContainerMimeType("text/foo")
+ .setSampleMimeType("text/plain")
+ .setCodecName("codec_1")
+ .setBitrate(1024)
+ .setLanguage("EN")
+ .setLanguageRegion("US")
+ .build();
+ s.reportTrackChangeEvent(e);
+ }
+
+ @Test
+ public void testNetworkEvent() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+ PlaybackSession s = manager.createPlaybackSession();
+ NetworkEvent e =
+ new NetworkEvent.Builder()
+ .setTimeSinceCreatedMillis(3032L)
+ .setNetworkType(NetworkEvent.NETWORK_TYPE_WIFI)
+ .build();
+ s.reportNetworkEvent(e);
+ }
+
+ @Test
+ public void testPlaybackMetrics() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+ PlaybackSession s = manager.createPlaybackSession();
+ PlaybackMetrics e =
+ new PlaybackMetrics.Builder()
+ .setMediaDurationMillis(233L)
+ .setStreamSource(PlaybackMetrics.STREAM_SOURCE_NETWORK)
+ .setStreamType(PlaybackMetrics.STREAM_TYPE_OTHER)
+ .setPlaybackType(PlaybackMetrics.PLAYBACK_TYPE_LIVE)
+ .setDrmType(PlaybackMetrics.DRM_TYPE_WIDEVINE_L1)
+ .setContentType(PlaybackMetrics.CONTENT_TYPE_MAIN)
+ .setPlayerName("ExoPlayer")
+ .setPlayerVersion("1.01x")
+ .setVideoFramesPlayed(1024)
+ .setVideoFramesDropped(32)
+ .setAudioUnderrunCount(22)
+ .setNetworkBytesRead(102400)
+ .setLocalBytesRead(2000)
+ .setNetworkTransferDurationMillis(6000)
+ .build();
+ s.reportPlaybackMetrics(e);
+ }
}
diff --git a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
index 1208432..43548043 100644
--- a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
+++ b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
@@ -79,4 +79,114 @@
assertThat(result.getPlaybackState().toString()).isEqualTo("JOINING_FOREGROUND");
assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(1763L);
}
+
+ public void testPlaybackErrorEvent() throws Exception {
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+ AtomsProto.Atom.MEDIA_PLAYBACK_ERROR_REPORTED_FIELD_NUMBER);
+ DeviceUtils.runDeviceTests(
+ getDevice(),
+ TEST_PKG,
+ "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+ "testPlaybackErrorEvent");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+ assertThat(data.size()).isEqualTo(1);
+ assertThat(data.get(0).getAtom().hasMediaPlaybackErrorReported()).isTrue();
+ AtomsProto.MediaPlaybackErrorReported result =
+ data.get(0).getAtom().getMediaPlaybackErrorReported();
+
+ assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(17630000L);
+ assertThat(result.getErrorCode().toString()).isEqualTo("ERROR_CODE_RUNTIME");
+ assertThat(result.getSubErrorCode()).isEqualTo(378);
+ assertThat(result.getExceptionStack().startsWith(
+ "android.media.metrics.cts.MediaMetricsAtomHostSideTests.testPlaybackErrorEvent"))
+ .isTrue();
+ }
+
+ public void testTrackChangeEvent_text() throws Exception {
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+ AtomsProto.Atom.MEDIA_PLAYBACK_TRACK_CHANGED_FIELD_NUMBER);
+ DeviceUtils.runDeviceTests(
+ getDevice(),
+ TEST_PKG,
+ "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+ "testTrackChangeEvent_text");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+ assertThat(data.size()).isEqualTo(1);
+ assertThat(data.get(0).getAtom().hasMediaPlaybackTrackChanged()).isTrue();
+ AtomsProto.MediaPlaybackTrackChanged result =
+ data.get(0).getAtom().getMediaPlaybackTrackChanged();
+
+ assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(37278L);
+ assertThat(result.getState().toString()).isEqualTo("ON");
+ assertThat(result.getReason().toString()).isEqualTo("REASON_MANUAL");
+ assertThat(result.getContainerMimeType()).isEqualTo("text/foo");
+ assertThat(result.getSampleMimeType()).isEqualTo("text/plain");
+ assertThat(result.getCodecName()).isEqualTo("codec_1");
+ assertThat(result.getBitrate()).isEqualTo(1024);
+ assertThat(result.getType().toString()).isEqualTo("TEXT");
+ assertThat(result.getLanguage()).isEqualTo("EN");
+ assertThat(result.getLanguageRegion()).isEqualTo("US");
+ }
+
+ public void testNetworkEvent() throws Exception {
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+ AtomsProto.Atom.MEDIA_NETWORK_INFO_CHANGED_FIELD_NUMBER);
+ DeviceUtils.runDeviceTests(
+ getDevice(),
+ TEST_PKG,
+ "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+ "testNetworkEvent");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+ assertThat(data.size()).isEqualTo(1);
+ assertThat(data.get(0).getAtom().hasMediaNetworkInfoChanged()).isTrue();
+ AtomsProto.MediaNetworkInfoChanged result =
+ data.get(0).getAtom().getMediaNetworkInfoChanged();
+
+ assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(3032L);
+ assertThat(result.getType().toString()).isEqualTo("NETWORK_TYPE_WIFI");
+ }
+
+ public void testPlaybackMetrics() throws Exception {
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+ AtomsProto.Atom.MEDIAMETRICS_PLAYBACK_REPORTED_FIELD_NUMBER);
+ DeviceUtils.runDeviceTests(
+ getDevice(),
+ TEST_PKG,
+ "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+ "testPlaybackMetrics");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ int appUid = DeviceUtils.getAppUid(getDevice(), TEST_PKG);
+
+ assertThat(data.size()).isEqualTo(1);
+ assertThat(data.get(0).getAtom().hasMediametricsPlaybackReported()).isTrue();
+ AtomsProto.MediametricsPlaybackReported result =
+ data.get(0).getAtom().getMediametricsPlaybackReported();
+
+ assertThat(result.getUid()).isEqualTo(appUid);
+ assertThat(result.getMediaDurationMillis()).isEqualTo(233L);
+ assertThat(result.getStreamSource().toString()).isEqualTo("STREAM_SOURCE_NETWORK");
+ assertThat(result.getStreamType().toString()).isEqualTo("STREAM_TYPE_OTHER");
+ assertThat(result.getPlaybackType().toString()).isEqualTo("PLAYBACK_TYPE_LIVE");
+ assertThat(result.getDrmType().toString()).isEqualTo("DRM_TYPE_WV_L1");
+ assertThat(result.getContentType().toString()).isEqualTo("CONTENT_TYPE_MAIN");
+ assertThat(result.getPlayerName()).isEqualTo("ExoPlayer");
+ assertThat(result.getPlayerVersion()).isEqualTo("1.01x");
+ assertThat(result.getVideoFramesPlayed()).isEqualTo(1024);
+ assertThat(result.getVideoFramesDropped()).isEqualTo(32);
+ assertThat(result.getAudioUnderrunCount()).isEqualTo(22);
+ assertThat(result.getNetworkBytesRead()).isEqualTo(102400);
+ assertThat(result.getLocalBytesRead()).isEqualTo(2000);
+ assertThat(result.getNetworkTransferDurationMillis()).isEqualTo(6000);
+ }
}
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp b/hostsidetests/packagemanager/installedloadingprogess/Android.bp
similarity index 95%
rename from hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
rename to hostsidetests/packagemanager/installedloadingprogess/Android.bp
index 72e5fef..6214dea 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
+++ b/hostsidetests/packagemanager/installedloadingprogess/Android.bp
@@ -15,7 +15,7 @@
java_test_host {
name: "CtsInstalledLoadingProgressHostTests",
defaults: ["cts_defaults"],
- srcs: ["src/**/*.java"],
+ srcs: ["hostside/src/**/*.java"],
libs: [
"cts-tradefed",
"tradefed",
@@ -28,4 +28,3 @@
"general-tests",
],
}
-
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/AndroidTest.xml b/hostsidetests/packagemanager/installedloadingprogess/AndroidTest.xml
similarity index 100%
rename from hostsidetests/packagemanager/installedloadingprogess/hostside/AndroidTest.xml
rename to hostsidetests/packagemanager/installedloadingprogess/AndroidTest.xml
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 634cf95..ce320f5 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -217,7 +217,8 @@
@Parameter(0)
public String mVolumeName;
- @Parameters
+ /** Parameters data. */
+ @Parameters(name = "volume={0}")
public static Iterable<? extends Object> data() {
return ScopedStorageDeviceTest.getTestParameters();
}
@@ -1348,6 +1349,39 @@
}
@Test
+ public void testDisableOpResetForSystemGallery() throws Exception {
+ final File otherAppImageFile = new File(getDcimDir(), "other_" + IMAGE_FILE_NAME);
+ final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
+
+ try {
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+ // Have another app create an image file
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppImageFile.getPath())).isTrue();
+ assertThat(otherAppImageFile.exists()).isTrue();
+
+ // Have another app create a video file
+ assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
+ assertThat(otherAppVideoFile.exists()).isTrue();
+
+ assertCanWriteAndRead(otherAppImageFile, BYTES_DATA1);
+ assertCanWriteAndRead(otherAppVideoFile, BYTES_DATA1);
+
+ // Reset app op should not reset System Gallery privileges
+ executeShellCommand("appops reset " + THIS_PACKAGE_NAME);
+
+ // Assert we can still write to images/videos
+ assertCanWriteAndRead(otherAppImageFile, BYTES_DATA2);
+ assertCanWriteAndRead(otherAppVideoFile, BYTES_DATA2);
+
+ } finally {
+ deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImageFile.getAbsolutePath());
+ deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppVideoFile.getAbsolutePath());
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ @Test
public void testSystemGalleryAppHasFullAccessToImages() throws Exception {
final File otherAppImageFile = new File(getDcimDir(), "other_" + IMAGE_FILE_NAME);
final File topLevelImageFile = new File(getExternalStorageDir(), IMAGE_FILE_NAME);
@@ -2576,6 +2610,14 @@
}
}
+ private void assertCanWriteAndRead(File file, byte[] data) throws Exception {
+ // Assert we can write to images/videos
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ fos.write(data);
+ }
+ assertFileContent(file, data);
+ }
+
/**
* Checks restrictions for opening pending and trashed files by different apps. Assumes that
* given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
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 edc615d..db782d1 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -137,6 +137,18 @@
}
@Test
+ public void testCheckInstallerAppCannotAccessDataDirs() throws Exception {
+ allowAppOps("android:request_install_packages");
+ grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+ try {
+ runDeviceTest("testCheckInstallerAppCannotAccessDataDirs");
+ } finally {
+ denyAppOps("android:request_install_packages");
+ revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+ }
+ }
+
+ @Test
public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
allowAppOps("android:manage_external_storage");
try {
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index cf5efe5..3f90dda 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -175,6 +175,27 @@
}
}
+ /**
+ * Test that Installer packages cannot access app's private directories in Android/data
+ */
+ @Test
+ public void testCheckInstallerAppCannotAccessDataDirs() throws Exception {
+ File[] dataDirs = getContext().getExternalFilesDirs(null);
+ for (File dataDir : dataDirs) {
+ final File otherAppExternalDataDir = new File(dataDir.getPath().replace(
+ THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
+ final File file = new File(otherAppExternalDataDir, NONMEDIA_FILE_NAME);
+ try {
+ assertThat(file.exists()).isFalse();
+
+ assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
+ assertCannotReadOrWrite(file);
+ } finally {
+ deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
+ }
+ }
+ }
+
@Test
public void testManageExternalStorageCanCreateFilesAnywhere() throws Exception {
pollForManageExternalStorageAllowed();
diff --git a/hostsidetests/securitybulletin/Android.bp b/hostsidetests/securitybulletin/Android.bp
index 5da0687..f256788 100644
--- a/hostsidetests/securitybulletin/Android.bp
+++ b/hostsidetests/securitybulletin/Android.bp
@@ -36,11 +36,12 @@
compile_multilib: "both",
multilib: {
lib32: {
- suffix: "32",
+ suffix: "_sts32",
},
lib64: {
- suffix: "64",
+ suffix: "_sts64",
},
+ // build/soong/common/arch.go default returns nil; no default possible
},
arch: {
arm: {
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp
index 6f087cc..10bbf66 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp
@@ -26,9 +26,6 @@
"frameworks/av/media/libmedia/include",
],
multilib: {
- lib32: {
- suffix: "32",
- },
lib64: {
shared_libs: [
"libstagefright",
@@ -37,7 +34,6 @@
"libstagefright_foundation",
"libdatasource",
],
- suffix: "64",
},
},
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2485/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2485/Android.bp
index c2b7636..630cb39 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2485/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2485/Android.bp
@@ -39,10 +39,6 @@
"android.hidl.allocator@1.0",
"android.hardware.media.omx@1.0",
],
- suffix: "32",
- },
- lib64: {
- suffix: "64",
},
},
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/Android.bp
index 2139583..a3928d7 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/Android.bp
@@ -39,10 +39,6 @@
"android.hidl.allocator@1.0",
"android.hardware.media.omx@1.0",
],
- suffix: "32",
- },
- lib64: {
- suffix: "64",
},
},
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2133/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2133/Android.bp
index a7eef92..eb42b96 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2133/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2133/Android.bp
@@ -22,9 +22,6 @@
"poc.cpp",
],
multilib: {
- lib32: {
- suffix: "32",
- },
lib64: {
include_dirs: [
"packages/apps/Nfc/nci/jni/extns/pn54x/src/mifare/",
@@ -39,7 +36,6 @@
shared_libs: [
"libnfc_nci_jni",
],
- suffix: "64",
},
},
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2134/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2134/Android.bp
index 3bbda28..c8353fe 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2134/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2134/Android.bp
@@ -22,9 +22,6 @@
"poc.cpp",
],
multilib: {
- lib32: {
- suffix: "32",
- },
lib64: {
include_dirs: [
"packages/apps/Nfc/nci/jni/extns/pn54x/src/mifare/",
@@ -39,7 +36,6 @@
shared_libs: [
"libnfc_nci_jni",
],
- suffix: "64",
},
},
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/Android.bp
index 70c3eed..c32cb4a 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/Android.bp
@@ -27,9 +27,6 @@
"-DENABLE_SELECTIVE_OVERLOADING",
],
multilib: {
- lib32: {
- suffix: "32",
- },
lib64: {
include_dirs: [
"system/nfc/src/nfc/include/",
@@ -41,7 +38,6 @@
shared_libs: [
"libnfc-nci",
],
- suffix: "64",
},
},
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp
index 1876c60..1c231b7 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp
@@ -22,14 +22,10 @@
"poc.cpp",
],
multilib: {
- lib32: {
- suffix: "32",
- },
lib64: {
shared_libs: [
"libmediandk",
],
- suffix: "64",
},
},
}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
index 82f6ed0..76bfeb8 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
@@ -182,7 +182,7 @@
String arguments, Map<String, String> envVars,
IShellOutputReceiver receiver) throws Exception {
String remoteFile = String.format("%s%s", TMP_PATH, pocName);
- SecurityTestCase.getPocPusher(device).pushFile(pocName, remoteFile);
+ SecurityTestCase.getPocPusher(device).pushFile(pocName + "_sts", remoteFile);
assertPocExecutable(pocName, device);
if (receiver == null) {
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0305.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0305.java
new file mode 100644
index 0000000..dd2aff8
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0305.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.security.cts;
+
+import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.platform.test.annotations.SecurityTest;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that collects test results from test package android.security.cts.CVE_2021_0305.
+ *
+ * When this test builds, it also builds a support APK containing
+ * {@link android.sample.cts.CVE_2021_0305.SampleDeviceTest}, the results of which are
+ * collected from the hostside and reported accordingly.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0305 extends BaseHostJUnit4Test {
+ private static final String TEST_PKG = "android.security.cts.CVE_2021_0305";
+ private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+ private static final String TEST_APP = "CVE-2021-0305.apk";
+
+ @Before
+ public void setUp() throws Exception {
+ uninstallPackage(getDevice(), TEST_PKG);
+ }
+
+ @Test
+ @SecurityTest(minPatchLevel = "2020-09")
+ @AppModeFull
+ public void testRunDeviceTestsPassesFull() throws Exception {
+ installPackage();
+ Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testClick"));
+ }
+
+ private void installPackage() throws Exception {
+ installPackage(TEST_APP, new String[0]);
+ }
+}
+
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/Android.bp
similarity index 62%
copy from hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
copy to hostsidetests/securitybulletin/test-apps/CVE-2021-0305/Android.bp
index 72e5fef..7001533 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/Android.bp
@@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,20 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-java_test_host {
- name: "CtsInstalledLoadingProgressHostTests",
- defaults: ["cts_defaults"],
- srcs: ["src/**/*.java"],
- libs: [
- "cts-tradefed",
- "tradefed",
- "compatibility-host-util",
- "cts-host-utils",
- ],
- static_libs: ["cts-install-lib-host"],
+android_test_helper_app {
+ name: "CVE-2021-0305",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
test_suites: [
"cts",
- "general-tests",
+ "vts10",
+ "sts",
],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.test.core",
+ ],
+ sdk_version: "current",
}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/AndroidManifest.xml
new file mode 100644
index 0000000..b63e97e
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/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"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="android.security.cts.CVE_2021_0305"
+ android:targetSandboxVersion="2">
+
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity
+ android:name=".MainActivity"
+ android:label="ST (Permission)"
+ android:taskAffinity="android.security.cts.CVE_2021_0305.MainActivity">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".OverlayActivity"
+ android:theme="@style/OverlayTheme"
+ android:exported="true"/>
+
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.security.cts.CVE_2021_0305" />
+
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/layout/activity_main.xml
new file mode 100644
index 0000000..20a9812
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/layout/activity_main.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ tools:context=".MainActivity">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ />
+
+ <Button
+ android:id="@+id/testButton1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/test_button_label"
+ android:layout_centerInParent="true"
+ android:gravity="end|center_vertical"
+ />
+</LinearLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/resources.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/resources.xml
new file mode 100644
index 0000000..815602c
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/resources.xml
@@ -0,0 +1,28 @@
+<resources>
+
+ <attr name="colorOverlay" format="color" />
+ <attr name="colorDialog" format="color" />
+ <attr name="projectAlpha" format="float" />
+
+ <!-- Switch between the two to see what's happening behind the attacks -->
+ <style name="AppTheme" parent="SeeBehind"/>
+ <!--<style name="AppTheme" parent="AppThemeBase.SeeBehind"/>-->
+
+ <style name="SeeBehind">
+ <item name="colorOverlay">#BB4BEFD7</item>
+ <item name="colorDialog">#88FFFFFF</item>
+ <item name="projectAlpha">0.0</item>
+ </style>
+
+ <color name="colorM">#5600D1</color>
+
+ <style name="OverlayTheme" parent="AppTheme">
+ <!--<item name="android:windowBackground">@color/colorM</item>-->
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ <item name="android:windowIsTranslucent">true</item>
+ </style>
+
+
+
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/strings.xml
new file mode 100644
index 0000000..ab8b450
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/res/values/strings.xml
@@ -0,0 +1,5 @@
+<resources>
+ <string name="app_name">CVE_2021_0305</string>
+ <string name="test_button_label">Test Button</string>
+ <string name="sample_text">text in activity</string>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/DeviceTest.java
new file mode 100644
index 0000000..5634f4f
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/DeviceTest.java
@@ -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.security.cts.CVE_2021_0305;
+
+import org.junit.Before;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.SystemClock;
+import android.util.Log;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+import androidx.test.uiautomator.BySelector;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Basic sample for unbundled UiAutomator.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+ private static final String BASIC_SAMPLE_PACKAGE
+ = "android.security.cts.CVE_2021_0305";
+ private static final int LAUNCH_TIMEOUT_MS = 20000;
+ private static final String STRING_TO_BE_TYPED = "UiAutomator";
+
+ private UiDevice mDevice;
+
+ @Before
+ public void startMainActivityFromHomeScreen() {
+
+ Log.d("CVE", "startMainActivityFromHomeScreen()");
+
+ // Initialize UiDevice instance
+ mDevice = UiDevice.getInstance(getInstrumentation());
+
+ // Start from the home screen
+ mDevice.pressHome();
+
+ // Launch the blueprint app
+ Context context = getApplicationContext();
+ assertThat(context, notNullValue());
+ PackageManager packageManager = context.getPackageManager();
+ assertThat(packageManager, notNullValue());
+ final Intent intent = packageManager.getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
+ assertThat(intent, notNullValue());
+
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances
+ context.startActivity(intent);
+
+ // Wait for the app to appear
+ Log.d("CVE", "wait for the app to appear");
+ mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT_MS);
+ Log.d("CVE", "app appeared");
+ }
+
+ @After
+ public void lastOperation(){
+ Log.d("CVE", "lastOperation() wait 20s");
+ SystemClock.sleep(20000);
+ Log.d("CVE", "lastOperation() completed");
+ }
+
+ @Test
+ public void testClick() {
+ Log.d("CVE", "testClick()");
+ boolean clicked = false;
+ java.util.List<UiObject2> objects;
+ BySelector selector = By.clickable(true);
+ String button;
+
+ //Detect "Test Button".
+ //"Test Button" appears after onResume().
+ //mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT_MS);
+ //waits for onEnterAnimationComplete() to finish.
+ //So we have LAUNCH_TIMEOUT_MS for the button to appear.
+ //If the button it still not available then
+ //we assume the button is obscured and the test passes.
+ Log.d("CVE", "looking for clickable");
+ objects = mDevice.findObjects(selector);
+ for (UiObject2 o : objects) {
+ button = o.getText();
+ Log.d("CVE", "button:" + button);
+
+ if(button==null){
+ //check the next button
+ continue;
+ }
+
+ switch(button){
+ case "Test Button" :
+ o.click();
+ clicked=true;
+ Log.i("CVE", "clicked: Test Button");
+ break;
+ default :
+ //check the next button
+ continue;
+ }
+
+ //A button this test is looking for just got pressed.
+ //Ignore remaining buttons
+ break;
+ }
+
+ Log.d("CVE", "testClick() end");
+ assertFalse(clicked);
+ Log.d("CVE", "testClick() passed");
+ }
+}
+
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/MainActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/MainActivity.java
new file mode 100644
index 0000000..50a1622
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/MainActivity.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.security.cts.CVE_2021_0305;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.Manifest.permission;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.util.Log;
+import android.widget.Button;
+import android.os.SystemClock;
+import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
+
+
+public class MainActivity extends Activity {
+
+ private static final int REQUEST_CAMERA_PERMISSION = 0;
+ Button testButton1;
+
+ @Override
+ public void onCreate(Bundle b) {
+ super.onCreate(b);
+ Log.e("CVE", "onCreate");
+ setContentView(R.layout.activity_main);
+ testButton1 = (Button) findViewById(R.id.testButton1);//get id of button
+
+ testButton1.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.d("CVE", "button click received");
+ }
+ });
+ }
+
+ @Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ Log.d("CVE", "MainActivity.onEnterAnimationComplete()");
+
+ //open OverlayActivity to obstruct MainActivity
+ Intent intent = new Intent(this, OverlayActivity.class);
+ intent.setFlags(FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(intent);
+ }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/OverlayActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/OverlayActivity.java
new file mode 100644
index 0000000..c320e5a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0305/src/android/security/cts/CVE_2021_0305/OverlayActivity.java
@@ -0,0 +1,4 @@
+package android.security.cts.CVE_2021_0305;
+import android.app.Activity;
+
+public class OverlayActivity extends Activity {}
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 f163fcf..090f743 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
@@ -203,6 +203,8 @@
APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS, 103);
APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_CREDENTIALS, 104);
APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, 105);
+ APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO_OUTPUT, 106);
+ APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, 107);
}
@Test
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
index d0915e6..1539a9d 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
@@ -41,6 +41,7 @@
ConfigUtils.removeConfig(getDevice());
ReportUtils.clearReports(getDevice());
DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+ DeviceUtils.turnScreenOn(getDevice());
Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
}
diff --git a/hostsidetests/tagging/Android.bp b/hostsidetests/tagging/Android.bp
index 7fc561f..8802542 100644
--- a/hostsidetests/tagging/Android.bp
+++ b/hostsidetests/tagging/Android.bp
@@ -1,33 +1,3 @@
-// Full truth table of whether tagging should be enabled. Note that a report from statsd is not
-// available if the kernel or manifest flag is disabled, as the zygote never probes compat (i.e.
-// mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING) never gets run). Also note that
-// disabling a compat feature is only supported on userdebug builds.
-//
-// +==================================================================================+
-// | # | Kernel | Manifest | SDK | Forced | Userdebug || Has | Report from |
-// | | Support | Flag | Level | Compat | Build || Tagging | statsd? |
-// | -- | ------- | -------- | ----- | -------- | --------- || ------- | ----------- |
-// | 1 | No | - | - | - | - || No | No |
-// | 2 | Yes | Off | - | - | - || No | No |
-// | 3 | Yes | None/On | 29 | Off/None | - || No | Yes |
-// | 4 | Yes | None/On | 29/30 | On | - || Yes | Yes |
-// | 5 | Yes | None/On | 30 | None | - || Yes | Yes |
-// | 6 | Yes | None/On | 30 | Off | Yes || No | Yes |
-// | 7 | Yes | None/On | 30 | Off | No || Yes | Yes |
-// +==================================================================================+
-//
-// And coverage of this truth table comes from:
-// #1. All tests on a device with an unsupported kernel.
-// #2. TaggingManifestDisabledTest.*
-// #3. Tagging(ManifestEnabled)?Sdk29.testDefault
-// #4. Tagging(ManifestEnabled)?Sdk(29|30).testCompatFeatureEnabled
-// #5. Tagging(ManifestEnabled)?Sdk30.testDefault
-// #6. Tagging(ManifestEnabled)?Sdk30.testCompatFeatureDisabledUserdebugBuild
-// #7. Tagging(ManifestEnabled)?Sdk30.testCompatFeatureDisabledUserBuild
-//
-// MTE tests are done at SDK level 30. The level doesn't really matter; there is
-// no manifest flag for MTE, and the compat feature is always disabled.
-
java_test_host {
name: "CtsTaggingHostTestCases",
defaults: ["cts_defaults"],
diff --git a/hostsidetests/tagging/common/jni/android_cts_tagging_Utils.cpp b/hostsidetests/tagging/common/jni/android_cts_tagging_Utils.cpp
index 45bedd9..af48ace 100644
--- a/hostsidetests/tagging/common/jni/android_cts_tagging_Utils.cpp
+++ b/hostsidetests/tagging/common/jni/android_cts_tagging_Utils.cpp
@@ -18,9 +18,11 @@
* Native implementation for the JniStaticTest parts.
*/
+#include <errno.h>
#include <jni.h>
#include <stdlib.h>
#include <sys/prctl.h>
+#include <sys/utsname.h>
extern "C" JNIEXPORT jboolean
Java_android_cts_tagging_Utils_kernelSupportsTaggedPointers() {
@@ -55,3 +57,13 @@
(void)load;
delete[] p;
}
+
+extern "C"
+JNIEXPORT jboolean JNICALL
+Java_android_cts_tagging_Utils_mistaggedKernelUaccessFails(JNIEnv *) {
+ auto *p = new utsname;
+ utsname* mistagged_p = reinterpret_cast<utsname*>(reinterpret_cast<uintptr_t>(p) + (1ULL << 56));
+ bool result = uname(mistagged_p) != 0 && errno == EFAULT;
+ delete p;
+ return result;
+}
diff --git a/hostsidetests/tagging/common/src/android/cts/tagging/Utils.java b/hostsidetests/tagging/common/src/android/cts/tagging/Utils.java
index 2897c9c..dbd62d3 100644
--- a/hostsidetests/tagging/common/src/android/cts/tagging/Utils.java
+++ b/hostsidetests/tagging/common/src/android/cts/tagging/Utils.java
@@ -23,4 +23,5 @@
public static native boolean kernelSupportsTaggedPointers();
public static native int nativeHeapPointerTag();
public static native void accessMistaggedPointer();
+ public static native boolean mistaggedKernelUaccessFails();
}
diff --git a/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml b/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml
index dae0c0f..07ac8a6 100644
--- a/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml
+++ b/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml
@@ -19,11 +19,8 @@
<uses-sdk android:targetSdkVersion="30" />
- <!-- Note, do not set debuggable:true. Pre-release platforms allow for
- debuggable apps to disable compat features, even if it's a `user`
- build. By using a non-debuggable app, we replicate a proper release-
- build device. -->
- <application android:allowNativeHeapPointerTagging="true">
+ <application android:debuggable="true"
+ android:allowNativeHeapPointerTagging="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/hostsidetests/tagging/sdk_30/AndroidManifest.xml b/hostsidetests/tagging/sdk_30/AndroidManifest.xml
index 7f8e393..cc1c6c4 100644
--- a/hostsidetests/tagging/sdk_30/AndroidManifest.xml
+++ b/hostsidetests/tagging/sdk_30/AndroidManifest.xml
@@ -21,11 +21,7 @@
<uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
- <!-- Note, do not set debuggable:true. Pre-release platforms allow for
- debuggable apps to disable compat features, even if it's a `user`
- build. By using a non-debuggable app, we replicate a proper release-
- build device. -->
- <application>
+ <application android:debuggable="true">
<uses-library android:name="android.test.runner" />
<processes>
<process />
diff --git a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
index ef73e96..6261de1 100644
--- a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
+++ b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
@@ -17,6 +17,7 @@
package android.cts.tagging.sdk30;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
@@ -89,11 +90,13 @@
mContext.startActivity(intent);
assertTrue(receiver.await());
+ assertTrue(Utils.mistaggedKernelUaccessFails());
}
@Test
public void testMemoryTagChecksDisabled() {
Utils.accessMistaggedPointer();
+ assertFalse(Utils.mistaggedKernelUaccessFails());
}
@Test
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
index bebd08f..30d6841 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
@@ -17,11 +17,8 @@
package com.android.cts.tagging;
import android.compat.cts.CompatChangeGatingTestCase;
-
import com.android.tradefed.device.ITestDevice;
-
import com.google.common.collect.ImmutableSet;
-
import java.util.Scanner;
public class TaggingBaseTest extends CompatChangeGatingTestCase {
@@ -29,8 +26,7 @@
private static final String DEVICE_KERNEL_HELPER_APK_NAME = "DeviceKernelHelpers.apk";
private static final String DEVICE_KERNEL_HELPER_PKG_NAME = "android.cts.tagging.support";
private static final String KERNEL_HELPER_START_COMMAND =
- String.format(
- "am start -W -a android.intent.action.MAIN -n %s/.%s",
+ String.format("am start -W -a android.intent.action.MAIN -n %s/.%s",
DEVICE_KERNEL_HELPER_PKG_NAME, DEVICE_KERNEL_HELPER_CLASS_NAME);
protected static final long NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID = 135754954;
@@ -38,59 +34,81 @@
protected static final String DEVICE_TAGGING_DISABLED_TEST_NAME = "testHeapTaggingDisabled";
protected static final String DEVICE_TAGGING_ENABLED_TEST_NAME = "testHeapTaggingEnabled";
- // Initialized in setUp(), holds whether the device that this test is running on was determined
- // to have both requirements for tagged pointers: the correct architecture (aarch64) and the
- // full set of kernel patches (as indicated by a successful prctl(PR_GET_TAGGED_ADDR_CTRL)).
- protected boolean deviceSupportsTaggedPointers = false;
// True if test device supports ARM MTE extension.
protected boolean deviceSupportsMemoryTagging = false;
// Initialized in setUp(), contains a set of pointer tagging changes that should be reported by
- // statsd. This set contains the compat change ID for heap tagging iff the device supports
- // tagged pointers (and is blank otherwise), as the kernel and manifest check in the zygote
- // happens before mPlatformCompat.isChangeEnabled(), and thus there's never a statsd entry for
- // the feature (in either the enabled or disabled state).
+ // statsd. This set contains the compat change ID for heap tagging iff we can guarantee a statsd
+ // report containing the compat change, and is empty otherwise. If the platform doesn't call
+ // mPlatformCompat.isChangeEnabled(), the statsd report doesn't contain an entry to the status
+ // of the corresponding compat feature. Compat isn't probed in a few scenarios: non-aarch64
+ // builds, if the kernel doesn't have support for tagged pointers, if the device supports MTE,
+ // or if the app has opted-out of the tagged pointers feature via. the manifest flag.
protected ImmutableSet reportedChangeSet = ImmutableSet.of();
// Initialized in setUp(), contains DEVICE_TAGGING_ENABLED_TEST_NAME iff the device supports
- // tagged pointers, and DEVICE_TAGGING_DISABLED_TEST_NAME otherwise.
+ // tagged pointers, and DEVICE_TAGGING_DISABLED_TEST_NAME otherwise. Note - if MTE hardware
+ // is present, the device does not support the tagged pointers feature.
protected String testForWhenSoftwareWantsTagging = DEVICE_TAGGING_DISABLED_TEST_NAME;
@Override
protected void setUp() throws Exception {
installPackage(DEVICE_KERNEL_HELPER_APK_NAME, true);
-
ITestDevice device = getDevice();
+
+ // Compat features have a very complicated truth table as to whether they can be
+ // enabled/disabled, including variants for:
+ // - Enabling vs. disabling.
+ // - `-userdebug` vs. "pre-release" `-user` vs. "release" `-user` builds.
+ // - `targetSdkLevel`-gated changes vs. default-enabled vs. default-disabled.
+ // - Debuggable vs. non-debuggable apps.
+ // We care most about compat features working correctly in the context of released `-user`
+ // builds, as these are what the customers of the compat features are most likely using. In
+ // order to ensure consistency here, we basically remove all these variables by reducing our
+ // device config permutations to a single set. All our apps are debuggable, and the
+ // following code forces the device to treat this test as a "released" `-user` build, which
+ // is the most restrictive and the most realistic w.r.t. what our users will use.
+ device.executeShellCommand(
+ "settings put global force_non_debuggable_final_build_for_compat 1");
+
+ // Kernel support for tagged pointers can only be determined on device.
+ // Deploy a helper package and observe what the kernel tells us about
+ // tagged pointers support.
device.executeAdbCommand("logcat", "-c");
device.executeShellCommand(KERNEL_HELPER_START_COMMAND);
- String logs =
- device.executeAdbCommand(
- "logcat",
- "-v",
- "brief",
- "-d",
- DEVICE_KERNEL_HELPER_CLASS_NAME + ":I",
- "*:S");
+ String logs = device.executeAdbCommand(
+ "logcat", "-v", "brief", "-d", DEVICE_KERNEL_HELPER_CLASS_NAME + ":I", "*:S");
+ // Holds whether the device that this test is running on was determined to have both
+ // requirements for ARM TBI: the correct architecture (aarch64) and the full set of kernel
+ // patches (as indicated by a successful prctl(PR_GET_TAGGED_ADDR_CTRL)).
+ boolean deviceHasTBI = false;
boolean foundKernelHelperResult = false;
Scanner in = new Scanner(logs);
while (in.hasNextLine()) {
String line = in.nextLine();
if (line.contains("Kernel supports tagged pointers")) {
foundKernelHelperResult = true;
- deviceSupportsTaggedPointers = line.contains("true");
+ deviceHasTBI = line.contains("true");
break;
}
}
in.close();
- uninstallPackage(DEVICE_KERNEL_HELPER_PKG_NAME, true);
if (!foundKernelHelperResult) {
throw new Exception("Failed to get a result from the kernel helper.");
}
deviceSupportsMemoryTagging = !runCommand("grep 'Features.* mte' /proc/cpuinfo").isEmpty();
- if (deviceSupportsTaggedPointers && !deviceSupportsMemoryTagging) {
+ if (deviceHasTBI && !deviceSupportsMemoryTagging) {
reportedChangeSet = ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID);
testForWhenSoftwareWantsTagging = DEVICE_TAGGING_ENABLED_TEST_NAME;
}
}
+
+ @Override
+ protected void tearDown() throws Exception {
+ uninstallPackage(DEVICE_KERNEL_HELPER_PKG_NAME, true);
+ ITestDevice device = getDevice();
+ device.executeShellCommand(
+ "settings put global force_non_debuggable_final_build_for_compat 0");
+ }
}
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java
index 97406c8..15c214e 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java
@@ -19,7 +19,6 @@
import com.google.common.collect.ImmutableSet;
public class TaggingManifestDisabledTest extends TaggingBaseTest {
-
protected static final String TEST_APK = "CtsHostsideTaggingManifestDisabledApp.apk";
protected static final String TEST_PKG = "android.cts.tagging.disabled";
@@ -32,33 +31,30 @@
@Override
protected void tearDown() throws Exception {
uninstallPackage(TEST_PKG, true);
+ super.tearDown();
}
- public void testDefault() throws Exception {
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ public void testHeapTaggingCompatFeatureDefault() throws Exception {
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
DEVICE_TAGGING_DISABLED_TEST_NAME,
/*enabledChanges*/ ImmutableSet.of(),
/*disabledChanges*/ ImmutableSet.of(),
/*reportedEnabledChanges*/ ImmutableSet.of(),
- // No statsd report for manifest-disabled apps, see truth table in
- // /cts/tagging/Android.bp for more information.
+ // No statsd report for manifest-disabled apps because the platform compat is never
+ // probed - see `reportedChangeSet` in TaggingBaseTest for more info.
/*reportedDisabledChanges*/ ImmutableSet.of());
}
- public void testCompatFeatureEnabled() throws Exception {
+ public void testHeapTaggingCompatFeatureEnabled() throws Exception {
// Trying to force the compat feature on should fail if the manifest specifically turns the
// feature off.
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
DEVICE_TAGGING_DISABLED_TEST_NAME,
/*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
/*disabledChanges*/ ImmutableSet.of(),
/*reportedEnabledChanges*/ ImmutableSet.of(),
- // No statsd report for manifest-disabled apps, see truth table in
- // /cts/tagging/Android.bp for more information.
+ // No statsd report for manifest-disabled apps because the platform compat is never
+ // probed - see `reportedChangeSet` in TaggingBaseTest for more info.
/*reportedDisabledChanges*/ ImmutableSet.of());
}
}
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java
index eff9f22..2549397 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java
@@ -19,7 +19,6 @@
import com.google.common.collect.ImmutableSet;
public class TaggingManifestEnabledSdk29Test extends TaggingBaseTest {
-
protected static final String TEST_APK = "CtsHostsideTaggingManifestEnabledSdk29App.apk";
protected static final String TEST_PKG = "android.cts.tagging.sdk29.manifest_enabled";
@@ -32,14 +31,13 @@
@Override
protected void tearDown() throws Exception {
uninstallPackage(TEST_PKG, true);
+ super.tearDown();
}
- public void testDefault() throws Exception {
+ public void testHeapTaggingCompatFeatureDefault() throws Exception {
// Even though the manifest enables tagged pointers, the targetSdkVersion still needs to be
// >= 30.
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
DEVICE_TAGGING_DISABLED_TEST_NAME,
/*enabledChanges*/ ImmutableSet.of(),
/*disabledChanges*/ ImmutableSet.of(),
@@ -47,10 +45,9 @@
/*reportedDisabledChanges*/ reportedChangeSet);
}
- public void testCompatFeatureEnabled() throws Exception {
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ public void testHeapTaggingCompatFeatureEnabled() throws Exception {
+ // We can still force the feature on though!
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
testForWhenSoftwareWantsTagging,
/*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
/*disabledChanges*/ ImmutableSet.of(),
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java
index f0ff916..ea8586e 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java
@@ -19,7 +19,6 @@
import com.google.common.collect.ImmutableSet;
public class TaggingManifestEnabledSdk30Test extends TaggingBaseTest {
-
protected static final String TEST_APK = "CtsHostsideTaggingManifestEnabledSdk30App.apk";
protected static final String TEST_PKG = "android.cts.tagging.sdk30.manifest_enabled";
@@ -32,12 +31,11 @@
@Override
protected void tearDown() throws Exception {
uninstallPackage(TEST_PKG, true);
+ super.tearDown();
}
- public void testDefault() throws Exception {
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ public void testHeapTaggingCompatFeatureDefault() throws Exception {
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
testForWhenSoftwareWantsTagging,
/*enabledChanges*/ ImmutableSet.of(),
/*disabledChanges*/ ImmutableSet.of(),
@@ -45,10 +43,8 @@
/*reportedDisabledChanges*/ ImmutableSet.of());
}
- public void testCompatFeatureEnabled() throws Exception {
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ public void testHeapTaggingCompatFeatureEnabled() throws Exception {
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
testForWhenSoftwareWantsTagging,
/*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
/*disabledChanges*/ ImmutableSet.of(),
@@ -56,35 +52,15 @@
/*reportedDisabledChanges*/ ImmutableSet.of());
}
- public void testCompatFeatureDisabledUserdebugBuild() throws Exception {
- // Userdebug build - check that we can force disable the compat feature at runtime.
- if (!getDevice().getBuildFlavor().contains("userdebug")) {
- return;
- }
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
- DEVICE_TAGGING_DISABLED_TEST_NAME,
- /*enabledChanges*/ ImmutableSet.of(),
- /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
- /*reportedEnabledChanges*/ ImmutableSet.of(),
- /*reportedDisabledChanges*/ reportedChangeSet);
- }
-
- public void testCompatFeatureDisabledUserBuild() throws Exception {
- // Non-userdebug build - we're not allowed to disable compat features. Check to ensure that
- // even if we try that we still get pointer tagging.
- if (getDevice().getBuildFlavor().contains("userdebug")
- || getDevice().getBuildFlavor().contains("eng")) {
- return;
- }
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ public void testHeapTaggingCompatFeatureDisabled() throws Exception {
+ // We're not allowed to disable compat features (see
+ // force_non_debuggable_final_build_for_compat in TaggingBaseTest for more info). Check to
+ // ensure that even if we try that we still get pointer tagging.
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
testForWhenSoftwareWantsTagging,
/*enabledChanges*/ ImmutableSet.of(),
/*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
- /*reportedEnabledChanges*/ reportedChangeSet,
+ /*reportedEnabledChanges*/ ImmutableSet.of(),
/*reportedDisabledChanges*/ ImmutableSet.of());
}
}
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java
index 776433e..f6ae6cf 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java
@@ -19,7 +19,6 @@
import com.google.common.collect.ImmutableSet;
public class TaggingSdk29Test extends TaggingBaseTest {
-
protected static final String TEST_APK = "CtsHostsideTaggingSdk29App.apk";
protected static final String TEST_PKG = "android.cts.tagging.sdk29";
@@ -32,12 +31,11 @@
@Override
protected void tearDown() throws Exception {
uninstallPackage(TEST_PKG, true);
+ super.tearDown();
}
- public void testDefault() throws Exception {
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ public void testHeapTaggingCompatFeatureDefault() throws Exception {
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
DEVICE_TAGGING_DISABLED_TEST_NAME,
/*enabledChanges*/ ImmutableSet.of(),
/*disabledChanges*/ ImmutableSet.of(),
@@ -45,10 +43,8 @@
/*reportedDisabledChanges*/ reportedChangeSet);
}
- public void testCompatFeatureEnabled() throws Exception {
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ public void testHeapTaggingCompatFeatureEnabled() throws Exception {
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
testForWhenSoftwareWantsTagging,
/*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
/*disabledChanges*/ ImmutableSet.of(),
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java
index fb3b288..c43a015 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java
@@ -19,7 +19,6 @@
import com.google.common.collect.ImmutableSet;
public class TaggingSdk30Test extends TaggingBaseTest {
-
protected static final String TEST_APK = "CtsHostsideTaggingSdk30App.apk";
protected static final String TEST_PKG = "android.cts.tagging.sdk30";
private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
@@ -36,9 +35,10 @@
@Override
protected void tearDown() throws Exception {
uninstallPackage(TEST_PKG, true);
+ super.tearDown();
}
- public void testHeapTaggingDefault() throws Exception {
+ public void testHeapTaggingCompatFeatureDefault() throws Exception {
runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
testForWhenSoftwareWantsTagging,
/*enabledChanges*/ ImmutableSet.of(),
@@ -48,9 +48,7 @@
}
public void testHeapTaggingCompatFeatureEnabled() throws Exception {
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
testForWhenSoftwareWantsTagging,
/*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
/*disabledChanges*/ ImmutableSet.of(),
@@ -58,35 +56,15 @@
/*reportedDisabledChanges*/ ImmutableSet.of());
}
- public void testCompatFeatureDisabledUserdebugBuild() throws Exception {
- // Userdebug build - check that we can force disable the compat feature at runtime.
- if (!getDevice().getBuildFlavor().contains("userdebug")) {
- return;
- }
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
- DEVICE_TAGGING_DISABLED_TEST_NAME,
- /*enabledChanges*/ ImmutableSet.of(),
- /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
- /*reportedEnabledChanges*/ ImmutableSet.of(),
- /*reportedDisabledChanges*/ reportedChangeSet);
- }
-
- public void testCompatFeatureDisabledUserBuild() throws Exception {
- // Non-userdebug build - we're not allowed to disable compat features. Check to ensure that
- // even if we try that we still get pointer tagging.
- if (getDevice().getBuildFlavor().contains("userdebug")
- || getDevice().getBuildFlavor().contains("eng")) {
- return;
- }
- runDeviceCompatTestReported(
- TEST_PKG,
- DEVICE_TEST_CLASS_NAME,
+ public void testHeapTaggingCompatFeatureDisabled() throws Exception {
+ // We're not allowed to disable compat features (see
+ // force_non_debuggable_final_build_for_compat in TaggingBaseTest for more info). Check to
+ // ensure that even if we try that we still get pointer tagging.
+ runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
testForWhenSoftwareWantsTagging,
/*enabledChanges*/ ImmutableSet.of(),
/*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
- /*reportedEnabledChanges*/ reportedChangeSet,
+ /*reportedEnabledChanges*/ ImmutableSet.of(),
/*reportedDisabledChanges*/ ImmutableSet.of());
}
@@ -96,7 +74,7 @@
}
runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagSyncChecksEnabled",
/*enabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_SYNC_CHANGE_ID),
- /*disabledChanges*/ImmutableSet.of());
+ /*disabledChanges*/ ImmutableSet.of());
}
public void testMemoryTagChecksAsyncCompatFeatureEnabled() throws Exception {
@@ -105,7 +83,7 @@
}
runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagAsyncChecksEnabled",
/*enabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_ASYNC_CHANGE_ID),
- /*disabledChanges*/ImmutableSet.of());
+ /*disabledChanges*/ ImmutableSet.of());
}
public void testMemoryTagChecksCompatFeatureDisabled() throws Exception {
@@ -118,13 +96,44 @@
ImmutableSet.of(NATIVE_MEMTAG_SYNC_CHANGE_ID, NATIVE_MEMTAG_ASYNC_CHANGE_ID));
}
+ // Ensure that enabling MTE on non-MTE hardware is a no-op. Note - No statsd report for
+ // NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID. The fallback for an app that requests MTE on non-MTE
+ // hardware is an implicit TBI. Compat is never probed for the status of the heap pointer
+ // tagging feature in this instance.
+ public void testMemoryTagChecksCompatFeatureEnabledNonMTE() throws Exception {
+ if (deviceSupportsMemoryTagging) {
+ return;
+ }
+ // Tagged Pointers should still be used if the kernel/HW supports it.
+ runDeviceCompatTestReported(TEST_PKG, ".TaggingTest", testForWhenSoftwareWantsTagging,
+ /*enabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_ASYNC_CHANGE_ID),
+ /*disabledChanges*/ ImmutableSet.of(),
+ // Don't check statsd report for NATIVE_MEMTAG_ASYNC_CHANGE_ID, as on non-aarch64
+ // we never probed compat for this feature.
+ /*reportedEnabledChanges*/ ImmutableSet.of(),
+ /*reportedDisabledChanges*/ ImmutableSet.of());
+ }
+
+ // Ensure that disabling MTE on non-MTE hardware is a no-op.
+ public void testMemoryTagChecksCompatFeatureDisabledNonMTE() throws Exception {
+ if (deviceSupportsMemoryTagging) {
+ return;
+ }
+ // Tagged Pointers should still be used if the kernel/HW supports it.
+ runDeviceCompatTestReported(TEST_PKG, ".TaggingTest", testForWhenSoftwareWantsTagging,
+ /*enabledChanges*/ ImmutableSet.of(),
+ /*disabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_ASYNC_CHANGE_ID),
+ /*reportedEnabledChanges*/ reportedChangeSet,
+ /*reportedDisabledChanges*/ ImmutableSet.of());
+ }
+
public void testMemoryTagChecksSyncActivity() throws Exception {
if (!deviceSupportsMemoryTagging) {
return;
}
runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagSyncActivityChecksEnabled",
/*enabledChanges*/ ImmutableSet.of(),
- /*disabledChanges*/ImmutableSet.of());
+ /*disabledChanges*/ ImmutableSet.of());
}
public void testMemoryTagChecksAsyncActivity() throws Exception {
@@ -133,6 +142,6 @@
}
runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagAsyncActivityChecksEnabled",
/*enabledChanges*/ ImmutableSet.of(),
- /*disabledChanges*/ImmutableSet.of());
+ /*disabledChanges*/ ImmutableSet.of());
}
}
diff --git a/libs/input/src/com/android/cts/input/HidLightTestData.java b/libs/input/src/com/android/cts/input/HidLightTestData.java
new file mode 100644
index 0000000..8a3bfb4
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/HidLightTestData.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 com.android.cts.input;
+
+import android.util.ArrayMap;
+
+/**
+ * Data class that stores HID light test data.
+ */
+public class HidLightTestData {
+ // Light type
+ public int lightType;
+
+ // Light color
+ public int lightColor;
+
+ // Light player ID
+ public int lightPlayerId;
+
+ // Light name
+ public String lightName;
+
+ // HID event type
+ public Byte hidEventType;
+
+ // HID report
+ public String report;
+
+ // Array of index and expected data in HID output packet to verify LED states.
+ public ArrayMap<Integer, Integer> expectedHidData;
+
+}
diff --git a/libs/input/src/com/android/cts/input/InputJsonParser.java b/libs/input/src/com/android/cts/input/InputJsonParser.java
index cec52e9..35a5df0 100644
--- a/libs/input/src/com/android/cts/input/InputJsonParser.java
+++ b/libs/input/src/com/android/cts/input/InputJsonParser.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import android.content.Context;
+import android.hardware.lights.Light;
import android.util.ArrayMap;
import android.util.SparseArray;
import android.view.InputDevice;
@@ -350,6 +351,41 @@
}
/**
+ * Read json resource, and return a {@code List} of HidLightTestData, which contains
+ * the light type and light state request, and the hid output verification data.
+ */
+ public List<HidLightTestData> getHidLightTestData(int resourceId) {
+ JSONArray json = getJsonArrayFromResource(resourceId);
+ List<HidLightTestData> tests = new ArrayList<HidLightTestData>();
+ for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+ HidLightTestData testData = new HidLightTestData();
+ try {
+ JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+ testData.lightType = lightTypeFromString(testcaseEntry.getString("lightType"));
+ testData.lightName = testcaseEntry.getString("lightName");
+ testData.lightColor = testcaseEntry.getInt("lightColor");
+ testData.lightPlayerId = testcaseEntry.getInt("lightPlayerId");
+ testData.hidEventType = uhidEventFromString(
+ testcaseEntry.getString("hidEventType"));
+ testData.report = testcaseEntry.getString("report");
+
+ JSONArray outputArray = testcaseEntry.getJSONArray("ledsHidOutput");
+ testData.expectedHidData = new ArrayMap<Integer, Integer>();
+ for (int i = 0; i < outputArray.length(); i++) {
+ JSONObject item = outputArray.getJSONObject(i);
+ int index = item.getInt("index");
+ int data = item.getInt("data");
+ testData.expectedHidData.put(index, data);
+ }
+ tests.add(testData);
+ } catch (JSONException e) {
+ throw new RuntimeException("Could not process entry " + testCaseNumber + " : " + e);
+ }
+ }
+ return tests;
+ }
+
+ /**
* Read json resource, and return a {@code List} of UinputVibratorTestData, which contains
* the vibrator FF effect of durations and amplitudes.
*/
@@ -679,4 +715,29 @@
}
throw new RuntimeException("Unknown button specified: " + button);
}
+
+ private static int lightTypeFromString(String typeString) {
+ switch (typeString.toUpperCase()) {
+ case "INPUT_SINGLE":
+ return Light.LIGHT_TYPE_INPUT_SINGLE;
+ case "INPUT_PLAYER_ID":
+ return Light.LIGHT_TYPE_INPUT_PLAYER_ID;
+ case "INPUT_RGB":
+ return Light.LIGHT_TYPE_INPUT_RGB;
+ }
+ throw new RuntimeException("Unknown light type specified: " + typeString);
+ }
+
+ // Return the enum uhid_event_type in kernel linux/uhid.h.
+ private static byte uhidEventFromString(String eventString) {
+ switch (eventString.toUpperCase()) {
+ case "UHID_OUTPUT":
+ return 6;
+ case "UHID_GET_REPORT":
+ return 9;
+ case "UHID_SET_REPORT":
+ return 13;
+ }
+ throw new RuntimeException("Unknown uhid event type specified: " + eventString);
+ }
}
diff --git a/tests/AlarmManager/AndroidManifest.xml b/tests/AlarmManager/AndroidManifest.xml
index 30395c0..8e10be6 100644
--- a/tests/AlarmManager/AndroidManifest.xml
+++ b/tests/AlarmManager/AndroidManifest.xml
@@ -17,8 +17,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.alarmmanager.cts" >
+ <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
+
<application android:label="Cts Alarm Manager Test">
<uses-library android:name="android.test.runner"/>
+
+ <receiver android:name=".AlarmReceiver" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/AlarmManager/app/AndroidManifest.xml b/tests/AlarmManager/app/AndroidManifest.xml
index f5b04b6..08b42f3 100644
--- a/tests/AlarmManager/app/AndroidManifest.xml
+++ b/tests/AlarmManager/app/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.alarmmanager.alarmtestapp.cts">
+ <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
+
<application>
<receiver android:name=".TestAlarmScheduler"
android:exported="true" />
diff --git a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
index 55ea6cf..0e7b8b5 100644
--- a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
@@ -26,11 +26,13 @@
private static final String PACKAGE_NAME = "android.alarmmanager.alarmtestapp.cts";
public static final String ACTION_REPORT_ALARM_EXPIRED = PACKAGE_NAME + ".action.ALARM_EXPIRED";
public static final String EXTRA_ALARM_COUNT = PACKAGE_NAME + ".extra.ALARM_COUNT";
+ public static final String EXTRA_ID = PACKAGE_NAME + ".extra.ID";
@Override
public void onReceive(Context context, Intent intent) {
final int count = intent.getIntExtra(Intent.EXTRA_ALARM_COUNT, 1);
- Log.d(TAG, "Alarm expired " + count + " times");
+ final long id = intent.getLongExtra(EXTRA_ID, -1);
+ Log.d(TAG, "Alarm " + id + " expired " + count + " times");
final Intent reportAlarmIntent = new Intent(ACTION_REPORT_ALARM_EXPIRED);
reportAlarmIntent.putExtra(EXTRA_ALARM_COUNT, count);
reportAlarmIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
diff --git a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
index 0623c79..991e165 100644
--- a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
@@ -21,6 +21,7 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.SystemClock;
import android.util.Log;
/**
@@ -45,10 +46,12 @@
final AlarmManager am = context.getSystemService(AlarmManager.class);
final Intent receiverIntent = new Intent(context, TestAlarmReceiver.class);
receiverIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final long id = SystemClock.elapsedRealtime();
+ receiverIntent.putExtra(TestAlarmReceiver.EXTRA_ID, id);
final PendingIntent alarmClockSender = PendingIntent.getBroadcast(context, 0,
- receiverIntent, PendingIntent.FLAG_MUTABLE);
+ receiverIntent, PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
final PendingIntent alarmSender = PendingIntent.getBroadcast(context, 1, receiverIntent,
- PendingIntent.FLAG_MUTABLE);
+ PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
switch (intent.getAction()) {
case ACTION_SET_ALARM_CLOCK:
if (!intent.hasExtra(EXTRA_ALARM_CLOCK_INFO)) {
@@ -57,7 +60,7 @@
}
final AlarmManager.AlarmClockInfo alarmClockInfo =
intent.getParcelableExtra(EXTRA_ALARM_CLOCK_INFO);
- Log.d(TAG, "Setting alarm clock " + alarmClockInfo);
+ Log.d(TAG, "Setting alarm clock " + alarmClockInfo + " id: " + id);
am.setAlarmClock(alarmClockInfo, alarmClockSender);
break;
case ACTION_SET_ALARM:
@@ -70,7 +73,7 @@
final long interval = intent.getLongExtra(EXTRA_REPEAT_INTERVAL, 0);
final boolean allowWhileIdle = intent.getBooleanExtra(EXTRA_ALLOW_WHILE_IDLE,
false);
- Log.d(TAG, "Setting alarm: type=" + type + ", triggerTime=" + triggerTime
+ Log.d(TAG, "Setting alarm: id=" + id + " type=" + type + ", triggerTime=" + triggerTime
+ ", interval=" + interval + ", allowWhileIdle=" + allowWhileIdle);
if (interval > 0) {
am.setRepeating(type, triggerTime, interval, alarmSender);
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AlarmReceiver.java b/tests/AlarmManager/src/android/alarmmanager/cts/AlarmReceiver.java
new file mode 100644
index 0000000..e3c6ee2
--- /dev/null
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AlarmReceiver.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.alarmmanager.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.LongArray;
+
+import androidx.annotation.GuardedBy;
+
+import java.util.Arrays;
+
+public class AlarmReceiver extends BroadcastReceiver {
+ private static final String TAG = AlarmReceiver.class.getSimpleName();
+ public static final String ALARM_ACTION = "android.alarmmanager.cts.ALARM";
+ public static final String EXTRA_ALARM_ID = "android.alarmmanager.cts.extra.ALARM_ID";
+ public static final String EXTRA_QUOTAED = "android.alarmmanager.cts.extra.QUOTAED";
+
+ static Object sWaitLock = new Object();
+
+ @GuardedBy("sWaitLock")
+ private static int sLastAlarmId;
+ /** Process global history of all alarms received -- useful in quota calculations */
+ private static LongArray sHistory = new LongArray();
+
+ static synchronized long getNthLastAlarmTime(int n) {
+ if (n <= 0 || n > sHistory.size()) {
+ return 0;
+ }
+ return sHistory.get(sHistory.size() - n);
+ }
+
+ private static synchronized void recordAlarmTime(long timeOfReceipt) {
+ if (sHistory.size() == 0 || sHistory.get(sHistory.size() - 1) < timeOfReceipt) {
+ sHistory.add(timeOfReceipt);
+ }
+ }
+
+ static boolean waitForAlarm(int alarmId, long timeOut) throws InterruptedException {
+ final long deadline = SystemClock.elapsedRealtime() + timeOut;
+ synchronized (sWaitLock) {
+ while (sLastAlarmId != alarmId && SystemClock.elapsedRealtime() < deadline) {
+ sWaitLock.wait(timeOut);
+ }
+ return sLastAlarmId == alarmId;
+ }
+ }
+
+ /**
+ * Used to dump debugging information when the test fails.
+ */
+ static void dumpState() {
+ synchronized (sWaitLock) {
+ Log.i(TAG, "Last id: " + sLastAlarmId);
+ }
+ synchronized (AlarmReceiver.class) {
+ if (sHistory.size() > 0) {
+ Log.i(TAG, "History of quotaed alarms: " + Arrays.toString(sHistory.toArray()));
+ }
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ALARM_ACTION.equals(intent.getAction())) {
+ final int id = intent.getIntExtra(EXTRA_ALARM_ID, -1);
+ final boolean quotaed = intent.getBooleanExtra(EXTRA_QUOTAED, false);
+ if (quotaed) {
+ recordAlarmTime(SystemClock.elapsedRealtime());
+ }
+ synchronized (sWaitLock) {
+ sLastAlarmId = id;
+ sWaitLock.notifyAll();
+ }
+ }
+ }
+}
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index a4456e1..3b384c2 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -49,7 +49,6 @@
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,7 +70,6 @@
private static final long POLL_INTERVAL = 200;
// Tweaked alarm manager constants to facilitate testing
- private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 10_000;
private static final long MIN_FUTURITY = 1_000;
// Not touching ACTIVE and RARE parameters for this test
@@ -145,13 +143,12 @@
assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled());
}
- private void scheduleAlarm(long triggerMillis, boolean allowWhileIdle, long interval) {
+ private void scheduleAlarm(long triggerMillis, long interval) {
final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM);
setAlarmIntent.setComponent(mAlarmScheduler);
setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, ELAPSED_REALTIME_WAKEUP);
setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis);
setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval);
- setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_ALLOW_WHILE_IDLE, allowWhileIdle);
setAlarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcast(setAlarmIntent);
}
@@ -182,13 +179,13 @@
firstTrigger + ((quota - 1) * MIN_FUTURITY) < desiredTrigger);
for (int i = 0; i < quota; i++) {
final long trigger = firstTrigger + (i * MIN_FUTURITY);
- scheduleAlarm(trigger, false, 0);
+ scheduleAlarm(trigger, 0);
Thread.sleep(trigger - SystemClock.elapsedRealtime());
assertTrue("Alarm within quota not firing as expected", waitForAlarm());
}
// Now quota is reached, any subsequent alarm should get deferred.
- scheduleAlarm(desiredTrigger, false, 0);
+ scheduleAlarm(desiredTrigger, 0);
Thread.sleep(desiredTrigger - SystemClock.elapsedRealtime());
assertFalse("Alarm exceeding quota not deferred", waitForAlarm());
final long minTrigger = firstTrigger + APP_STANDBY_WINDOW;
@@ -201,7 +198,7 @@
setAppStandbyBucket("active");
long nextTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
for (int i = 0; i < 3; i++) {
- scheduleAlarm(nextTrigger, false, 0);
+ scheduleAlarm(nextTrigger, 0);
Thread.sleep(MIN_FUTURITY);
assertTrue("Alarm not received as expected when app is in active", waitForAlarm());
nextTrigger += MIN_FUTURITY;
@@ -227,7 +224,7 @@
public void testNeverQuota() throws Exception {
setAppStandbyBucket("never");
final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- scheduleAlarm(expectedTrigger, true, 0);
+ scheduleAlarm(expectedTrigger, 0);
Thread.sleep(10_000);
assertFalse("Alarm received when app was in never bucket", waitForAlarm());
}
@@ -242,43 +239,11 @@
}
@Test
- @Ignore("Broken until b/171306433 is completed")
- public void testAllowWhileIdleAlarms() throws Exception {
- setAppStandbyBucket("active");
- final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- scheduleAlarm(firstTrigger, true, 0);
- Thread.sleep(MIN_FUTURITY);
- assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarm());
- long lastTriggerTime = sAlarmHistory.getLast(1);
- scheduleAlarm(lastTriggerTime + ALLOW_WHILE_IDLE_SHORT_TIME / 3, true, 0);
- // First check for the case where allow_while_idle delay should supersede app standby
- setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
- Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME / 2);
- assertFalse("allow_while_idle alarm went off before short time", waitForAlarm());
- long expectedTriggerTime = lastTriggerTime + ALLOW_WHILE_IDLE_SHORT_TIME;
- Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
- assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarm());
-
- // Now the other case, app standby delay supersedes the allow_while_idle delay
- lastTriggerTime = sAlarmHistory.getLast(1);
- scheduleAlarm(lastTriggerTime + APP_STANDBY_WINDOW / 10, true, 0);
- setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
- Thread.sleep(APP_STANDBY_WINDOW / 20);
- assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_WINDOW
- + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
- expectedTriggerTime = lastTriggerTime + APP_STANDBY_WINDOW;
- Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
- assertTrue("allow_while_idle alarm did not go off even after "
- + APP_STANDBY_WINDOW
- + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
- }
-
- @Test
public void testPowerWhitelistedAlarmNotBlocked() throws Exception {
setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
setPowerWhitelisted(true);
final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
- scheduleAlarm(triggerTime, false, 0);
+ scheduleAlarm(triggerTime, 0);
Thread.sleep(MIN_FUTURITY);
assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarm());
setPowerWhitelisted(false);
@@ -306,8 +271,6 @@
private void updateAlarmManagerConstants() {
mAlarmManagerDeviceConfigStateHelper.set("min_futurity", String.valueOf(MIN_FUTURITY));
- mAlarmManagerDeviceConfigStateHelper.set("allow_while_idle_short_time",
- String.valueOf(ALLOW_WHILE_IDLE_SHORT_TIME));
mAlarmManagerDeviceConfigStateHelper.set("app_standby_window",
String.valueOf(APP_STANDBY_WINDOW));
for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
index 81ed9c0..4257689 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
@@ -65,21 +65,19 @@
private static final long POLL_INTERVAL = 200;
private static final long MIN_REPEATING_INTERVAL = 10_000;
- private Object mLock = new Object();
private Context mContext;
private ComponentName mAlarmScheduler;
private DeviceConfigStateHelper mDeviceConfigStateHelper;
private UiDevice mUiDevice;
- private int mAlarmCount;
+ private volatile int mAlarmCount;
private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ mAlarmCount = intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1);
Log.d(TAG, "Received action " + intent.getAction()
+ " elapsed: " + SystemClock.elapsedRealtime());
- synchronized (mLock) {
- mAlarmCount = intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1);
- }
+
}
};
@@ -92,7 +90,9 @@
mDeviceConfigStateHelper =
new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ALARM_MANAGER);
updateAlarmManagerConstants();
+ updateBackgroundSettleTime();
setAppOpsMode(APP_OP_MODE_IGNORED);
+ makeUidIdle();
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
@@ -156,6 +156,7 @@
@After
public void tearDown() throws Exception {
deleteAlarmManagerConstants();
+ resetBackgroundSettleTime();
setAppOpsMode(APP_OP_MODE_ALLOWED);
// Cancel any leftover alarms
final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
@@ -189,14 +190,26 @@
mUiDevice.executeShellCommand(commandBuilder.toString());
}
+ private void updateBackgroundSettleTime() throws IOException {
+ mUiDevice.executeShellCommand(
+ "settings put global activity_manager_constants background_settle_time=100");
+ }
+
+ private void resetBackgroundSettleTime() throws IOException {
+ mUiDevice.executeShellCommand("settings delete global activity_manager_constants");
+ }
+
+ private void makeUidIdle() throws IOException {
+ mUiDevice.executeShellCommand("cmd devideidle tempwhitelist -r " + TEST_APP_PACKAGE);
+ mUiDevice.executeShellCommand("am make-uid-idle " + TEST_APP_PACKAGE);
+ }
+
private boolean waitForAlarms(int expectedAlarms, long timeout) throws InterruptedException {
final long deadLine = SystemClock.uptimeMillis() + timeout;
int alarmCount;
do {
Thread.sleep(POLL_INTERVAL);
- synchronized (mLock) {
- alarmCount = mAlarmCount;
- }
+ alarmCount = mAlarmCount;
} while (alarmCount < expectedAlarms && SystemClock.uptimeMillis() < deadLine);
return alarmCount >= expectedAlarms;
}
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
index 86df15e..2d7397d 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
@@ -16,6 +16,9 @@
package android.alarmmanager.cts;
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.RTC_WAKEUP;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -26,6 +29,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
import android.provider.DeviceConfig;
import android.util.Log;
@@ -48,6 +52,7 @@
/**
* Tests that system time changes are handled appropriately for alarms
*/
+@AppModeFull
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TimeChangeTests {
@@ -98,7 +103,7 @@
final Intent alarmIntent = new Intent(ACTION_ALARM)
.setPackage(mContext.getPackageName())
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, PendingIntent.FLAG_MUTABLE);
final IntentFilter alarmFilter = new IntentFilter(ACTION_ALARM);
mContext.registerReceiver(mAlarmReceiver, alarmFilter);
mDeviceConfigStateHelper =
@@ -116,8 +121,7 @@
public void elapsedAlarmsUnaffected() throws Exception {
final long delayElapsed = 5_000;
final long expectedTriggerElapsed = mTestStartElapsed + delayElapsed;
- mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- expectedTriggerElapsed, mAlarmPi);
+ mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP, expectedTriggerElapsed, mAlarmPi);
final long newRtc = mTestStartRtc - 32 * MILLIS_IN_MINUTE; // arbitrary, shouldn't matter
setTime(newRtc);
Thread.sleep(delayElapsed);
@@ -130,8 +134,7 @@
final long newRtc = mTestStartRtc + 14 * MILLIS_IN_MINUTE; // arbitrary, but in the future
final long delayRtc = 4_231;
final long expectedTriggerRtc = newRtc + delayRtc;
- mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, expectedTriggerRtc,
- mAlarmPi);
+ mAlarmManager.setExact(RTC_WAKEUP, expectedTriggerRtc, mAlarmPi);
Thread.sleep(delayRtc);
assertFalse("Alarm fired before time was changed",
mAlarmLatch.await(DEFAULT_WAIT_MILLIS, TimeUnit.MILLISECONDS));
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/WhileIdleAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/WhileIdleAlarmsTest.java
new file mode 100644
index 0000000..41d6555
--- /dev/null
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/WhileIdleAlarmsTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.alarmmanager.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.PowerWhitelistManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Random;
+
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class WhileIdleAlarmsTest {
+ /**
+ * TODO (b/171306433): Add tests for the following:
+ *
+ * Pre-S apps can:
+ * - use setAlarmClock freely -- no temp-allowlist
+ * - use setExactAndAWI with 7 / hr quota with standby and temp-allowlist
+ * - use setInexactAndAWI with 7 / hr quota with standby-bucket "ACTIVE" and temp-allowlist
+ *
+ * S+ apps with permission can:
+ * - use setInexactAWI with low quota + standby and no temp-allowlist.
+ * - use setInexactAWI with whitelist if in the whitelist.
+ */
+ private static final String TAG = WhileIdleAlarmsTest.class.getSimpleName();
+
+ private static final int ALLOW_WHILE_IDLE_QUOTA = 5;
+ private static final long ALLOW_WHILE_IDLE_WINDOW = 10_000;
+ private static final int ALLOW_WHILE_IDLE_COMPAT_QUOTA = 3;
+
+ /**
+ * Waiting generously long for success because the system can sometimes be slow to
+ * provide expected behavior.
+ * A different and shorter duration should be used while waiting for no-failure, because
+ * even if the system is slow to fail in some cases, it would still cause some
+ * flakiness and get flagged for investigation.
+ */
+ private static final long DEFAULT_WAIT_FOR_SUCCESS = 30_000;
+
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private final AlarmManager mAlarmManager = sContext.getSystemService(AlarmManager.class);
+ private final PowerWhitelistManager mWhitelistManager = sContext.getSystemService(
+ PowerWhitelistManager.class);
+
+ private final DeviceConfigStateHelper mDeviceConfigHelper = new DeviceConfigStateHelper(
+ DeviceConfig.NAMESPACE_ALARM_MANAGER);
+ private final Random mIdGenerator = new Random(6789);
+
+ @Rule
+ public TestWatcher mFailLoggerRule = new TestWatcher() {
+ @Override
+ protected void failed(Throwable e, Description description) {
+ Log.i(TAG, "Debugging info for failed test: " + description.getMethodName());
+ Log.i(TAG, SystemUtil.runShellCommand("dumpsys alarm"));
+ AlarmReceiver.dumpState();
+ }
+ };
+
+ @Before
+ @After
+ public void resetAppOp() throws IOException {
+ AppOpsUtils.reset(sContext.getOpPackageName());
+ }
+
+ @Before
+ public void updateAlarmManagerConstants() {
+ mDeviceConfigHelper.set("min_futurity", "0");
+ mDeviceConfigHelper.set("allow_while_idle_quota", String.valueOf(ALLOW_WHILE_IDLE_QUOTA));
+ mDeviceConfigHelper.set("allow_while_idle_compat_quota",
+ String.valueOf(ALLOW_WHILE_IDLE_COMPAT_QUOTA));
+ mDeviceConfigHelper.set("allow_while_idle_window", String.valueOf(ALLOW_WHILE_IDLE_WINDOW));
+ }
+
+ @Before
+ public void putDeviceToIdle() {
+ SystemUtil.runShellCommandForNoOutput("dumpsys battery reset");
+ SystemUtil.runShellCommand("cmd deviceidle force-idle deep");
+ }
+
+ @Before
+ public void enableChange() {
+ SystemUtil.runShellCommand("am compat enable --no-kill REQUIRE_EXACT_ALARM_PERMISSION "
+ + sContext.getOpPackageName(), output -> output.contains("Enabled"));
+ }
+
+ @After
+ public void resetChanges() {
+ // This is needed because compat persists the overrides beyond package uninstall
+ SystemUtil.runShellCommand("am compat reset --no-kill REQUIRE_EXACT_ALARM_PERMISSION "
+ + sContext.getOpPackageName(), output -> output.contains("Reset"));
+ }
+
+ @After
+ public void removeFromWhitelists() {
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mWhitelistManager.removeFromWhitelist(sContext.getOpPackageName()));
+ SystemUtil.runShellCommand("cmd deviceidle tempwhitelist -r "
+ + sContext.getOpPackageName());
+ }
+
+ @After
+ public void restoreBatteryState() {
+ SystemUtil.runShellCommand("cmd deviceidle unforce");
+ SystemUtil.runShellCommandForNoOutput("dumpsys battery reset");
+ }
+
+ @After
+ public void restoreAlarmManagerConstants() {
+ mDeviceConfigHelper.restoreOriginalValues();
+ }
+
+ private static void revokeAppOp() throws IOException {
+ AppOpsUtils.setOpMode(sContext.getOpPackageName(), AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+ AppOpsManager.MODE_IGNORED);
+ }
+
+ private static PendingIntent getAlarmSender(int id, boolean quotaed) {
+ final Intent alarmAction = new Intent(AlarmReceiver.ALARM_ACTION)
+ .setClass(sContext, AlarmReceiver.class)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(AlarmReceiver.EXTRA_ALARM_ID, id)
+ .putExtra(AlarmReceiver.EXTRA_QUOTAED, quotaed);
+ return PendingIntent.getBroadcast(sContext, 0, alarmAction,
+ PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ @Test
+ public void hasPermissionByDefault() {
+ assertTrue(mAlarmManager.canScheduleExactAlarms());
+ }
+
+ @Test
+ public void noPermissionWhenIgnored() throws IOException {
+ revokeAppOp();
+ assertFalse(mAlarmManager.canScheduleExactAlarms());
+ }
+
+ @Test
+ public void hasPermissionWhenAllowed() throws IOException {
+ AppOpsUtils.setOpMode(sContext.getOpPackageName(), AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+ AppOpsManager.MODE_ALLOWED);
+ assertTrue(mAlarmManager.canScheduleExactAlarms());
+ }
+
+ @Test(expected = SecurityException.class)
+ public void setAlarmClockWithoutPermission() throws IOException {
+ revokeAppOp();
+ mAlarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(0, null), getAlarmSender(0,
+ false));
+ }
+
+ private void whitelistTestApp() {
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mWhitelistManager.addToWhitelist(sContext.getOpPackageName()));
+ }
+
+ @Test(expected = SecurityException.class)
+ public void setAlarmClockWithoutPermissionWithWhitelist() throws IOException {
+ revokeAppOp();
+ whitelistTestApp();
+ mAlarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(0, null), getAlarmSender(0,
+ false));
+ }
+
+ @Test
+ public void setAlarmClockWithPermission() throws Exception {
+ final long now = System.currentTimeMillis();
+ final int numAlarms = 100; // Number much higher than any quota.
+ for (int i = 0; i < numAlarms; i++) {
+ final int id = mIdGenerator.nextInt();
+ final AlarmManager.AlarmClockInfo alarmClock = new AlarmManager.AlarmClockInfo(now,
+ null);
+ mAlarmManager.setAlarmClock(alarmClock, getAlarmSender(id, false));
+ assertTrue("Alarm " + id + " not received",
+ AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
+ }
+ }
+
+ @Test(expected = SecurityException.class)
+ public void setExactAwiWithoutPermissionOrWhitelist() throws IOException {
+ revokeAppOp();
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, 0,
+ getAlarmSender(0, false));
+ }
+
+ @Test
+ public void setExactAwiWithoutPermissionWithWhitelist() throws Exception {
+ revokeAppOp();
+ whitelistTestApp();
+ final long now = SystemClock.elapsedRealtime();
+ // This is the user whitelist, so the app should get unrestricted alarms.
+ final int numAlarms = 100; // Number much higher than any quota.
+ for (int i = 0; i < numAlarms; i++) {
+ final int id = mIdGenerator.nextInt();
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now,
+ getAlarmSender(id, false));
+ assertTrue("Alarm " + id + " not received",
+ AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
+ }
+ }
+
+ @Test
+ public void setExactAwiWithPermissionAndWhitelist() throws Exception {
+ whitelistTestApp();
+ final long now = SystemClock.elapsedRealtime();
+ // The user whitelist takes precedence, so the app should get unrestricted alarms.
+ final int numAlarms = 100; // Number much higher than any quota.
+ for (int i = 0; i < numAlarms; i++) {
+ final int id = mIdGenerator.nextInt();
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now,
+ getAlarmSender(id, false));
+ assertTrue("Alarm " + id + " not received",
+ AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
+ }
+ }
+
+ private static void reclaimQuota(int quotaToReclaim) {
+ final long eligibleAt = getNextEligibleTime(quotaToReclaim);
+ long now;
+ while ((now = SystemClock.elapsedRealtime()) < eligibleAt) {
+ try {
+ Thread.sleep(eligibleAt - now);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Thread interrupted while reclaiming quota!", e);
+ }
+ }
+ }
+
+ private static long getNextEligibleTime(int quotaToReclaim) {
+ long t = AlarmReceiver.getNthLastAlarmTime(ALLOW_WHILE_IDLE_QUOTA - quotaToReclaim + 1);
+ return t + ALLOW_WHILE_IDLE_WINDOW;
+ }
+
+ @Test
+ @Ignore("Flaky on cuttlefish") // TODO (b/171306433): Fix and re-enable
+ public void setExactAwiWithPermissionWithoutWhitelist() throws Exception {
+ reclaimQuota(ALLOW_WHILE_IDLE_QUOTA);
+
+ int alarmId;
+ for (int i = 0; i < ALLOW_WHILE_IDLE_QUOTA; i++) {
+ final long trigger = SystemClock.elapsedRealtime() + 500;
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, trigger,
+ getAlarmSender(alarmId = mIdGenerator.nextInt(), true));
+ Thread.sleep(500);
+ assertTrue("Alarm " + alarmId + " not received",
+ AlarmReceiver.waitForAlarm(alarmId, DEFAULT_WAIT_FOR_SUCCESS));
+ }
+ long now = SystemClock.elapsedRealtime();
+ final long nextTrigger = getNextEligibleTime(1);
+ assertTrue("Not enough margin to test reliably", nextTrigger > now + 5000);
+
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now,
+ getAlarmSender(alarmId = mIdGenerator.nextInt(), true));
+ assertFalse("Alarm received when no quota", AlarmReceiver.waitForAlarm(alarmId, 5000));
+
+ now = SystemClock.elapsedRealtime();
+ if (now < nextTrigger) {
+ Thread.sleep(nextTrigger - now);
+ }
+ assertTrue("Alarm " + alarmId + " not received when back in quota",
+ AlarmReceiver.waitForAlarm(alarmId, DEFAULT_WAIT_FOR_SUCCESS));
+ }
+
+ private static void assertTempWhitelistState(boolean whitelisted) {
+ final String selfUid = String.valueOf(Process.myUid());
+ SystemUtil.runShellCommand("cmd deviceidle tempwhitelist",
+ output -> (output.contains(selfUid) == whitelisted));
+ }
+
+ @Test
+ public void alarmClockGrantsWhitelist() throws Exception {
+ final int id = mIdGenerator.nextInt();
+ final AlarmManager.AlarmClockInfo alarmClock = new AlarmManager.AlarmClockInfo(
+ System.currentTimeMillis() + 100, null);
+ mAlarmManager.setAlarmClock(alarmClock, getAlarmSender(id, false));
+ Thread.sleep(100);
+ assertTrue("Alarm " + id + " not received", AlarmReceiver.waitForAlarm(id,
+ DEFAULT_WAIT_FOR_SUCCESS));
+ assertTempWhitelistState(true);
+ }
+
+ @Test
+ public void exactAwiGrantsWhitelist() throws Exception {
+ reclaimQuota(1);
+ final int id = mIdGenerator.nextInt();
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + 100, getAlarmSender(id, true));
+ Thread.sleep(100);
+ assertTrue("Alarm " + id + " not received", AlarmReceiver.waitForAlarm(id,
+ DEFAULT_WAIT_FOR_SUCCESS));
+ assertTempWhitelistState(true);
+ }
+}
diff --git a/tests/admin/AndroidTest.xml b/tests/admin/AndroidTest.xml
index b1acd9a..fccdc2b 100644
--- a/tests/admin/AndroidTest.xml
+++ b/tests/admin/AndroidTest.xml
@@ -49,7 +49,7 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.admin.cts" />
<option name="runtime-hint" value="17m" />
- <option name="exclude-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
</test>
</configuration>
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index d1085c7..a8d93b5 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -41,6 +41,8 @@
import android.test.suitebuilder.annotation.Suppress;
import android.util.Log;
+import com.android.compatibility.common.util.SystemUtil;
+
import java.io.ByteArrayInputStream;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
@@ -114,6 +116,34 @@
assertTrue(mDevicePolicyManager.isAdminActive(mComponent));
}
+ public void testSetGetNetworkSlicingEnabled() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping SetGetNetworkSlicingEnabled");
+ return;
+ }
+
+
+ try {
+ mDevicePolicyManager.clearProfileOwner(DeviceAdminInfoTest.getProfileOwnerComponent());
+ assertThrows(SecurityException.class,
+ () -> mDevicePolicyManager.setNetworkSlicingEnabled(true));
+
+ assertThrows(SecurityException.class,
+ () -> mDevicePolicyManager.isNetworkSlicingEnabled());
+
+ assertThrows(SecurityException.class,
+ () -> mDevicePolicyManager.isNetworkSlicingEnabledForUser(
+ Process.myUserHandle()));
+ } catch (SecurityException se) {
+ Log.w(TAG, "Test is not a profile owner and there is no need to clear.");
+ } finally {
+ SystemUtil.runShellCommand(
+ "dpm set-profile-owner --user cur "
+ + DeviceAdminInfoTest.getProfileOwnerComponent().flattenToString());
+ }
+
+ }
+
public void testKeyguardDisabledFeatures() {
if (!mDeviceAdmin) {
Log.w(TAG, "Skipping testKeyguardDisabledFeatures");
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index 25cc5ae..5332191 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -134,11 +134,14 @@
"compatibility-device-util-axt",
"CtsExternalServiceCommon",
"cts-wm-util",
+ "libprotobuf-java-lite",
],
srcs: [
+ ":libtombstone_proto-src",
"AppExitTest/src/**/*.java",
"src/android/app/cts/android/app/cts/tools/WatchUidRunner.java",
],
+ jarjar_rules: "AppExitTest/jarjar-rules.txt",
test_suites: [
"cts",
"general-tests",
diff --git a/tests/app/AppExitTest/jarjar-rules.txt b/tests/app/AppExitTest/jarjar-rules.txt
new file mode 100644
index 0000000..88da88e
--- /dev/null
+++ b/tests/app/AppExitTest/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.android.server.os.** android.app.cts.appexit.@1
diff --git a/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java b/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
index fe194f2..77973bb 100644
--- a/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
+++ b/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
@@ -59,9 +59,11 @@
import com.android.compatibility.common.util.SystemUtil;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.MemInfoReader;
+import com.android.server.os.TombstoneProtos.Tombstone;
import java.io.BufferedInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -832,6 +834,21 @@
assertTrue(list != null && list.size() == 1);
verify(list.get(0), mStubPackagePid, mStubPackageUid, STUB_PACKAGE_NAME,
ApplicationExitInfo.REASON_CRASH_NATIVE, null, null, now, now2);
+
+ InputStream trace = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ list.get(0),
+ (i) -> {
+ try {
+ return i.getTraceInputStream();
+ } catch (IOException ex) {
+ return null;
+ }
+ },
+ android.Manifest.permission.DUMP);
+
+ assertNotNull(trace);
+ Tombstone tombstone = Tombstone.parseFrom(trace);
+ assertEquals(tombstone.getPid(), mStubPackagePid);
}
public void testUserRequested() throws Exception {
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 6dcd647..958fe45 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -30,6 +30,7 @@
import android.accessibilityservice.AccessibilityService;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
+import android.app.ForegroundServiceStartNotAllowedException;
import android.app.Instrumentation;
import android.app.cts.android.app.cts.tools.WaitForBroadcast;
import android.app.cts.android.app.cts.tools.WatchUidRunner;
@@ -1002,18 +1003,18 @@
// At Context.startForegroundService() or Service.startForeground() calls, if the FGS is
// restricted by background restriction and the app's targetSdkVersion is at least S, the
- // framework throws a IllegalStateException with error message.
+ // framework throws a ForegroundServiceStartNotAllowedException with error message.
@Test
@Ignore("The instrumentation is allowed to star FGS, it does not throw the exception")
public void testFgsStartFromBGException() throws Exception {
- IllegalStateException expectedException = null;
+ ForegroundServiceStartNotAllowedException expectedException = null;
final Intent intent = new Intent().setClassName(
PACKAGE_NAME_APP1, "android.app.stubs.LocalForegroundService");
try {
allowBgActivityStart("android.app.stubs", false);
enableFgsRestriction(true, true, null);
mContext.startForegroundService(intent);
- } catch (IllegalStateException e) {
+ } catch (ForegroundServiceStartNotAllowedException e) {
expectedException = e;
} finally {
mContext.stopService(intent);
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java
index 90a74f8..b6bb7c6 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java
@@ -24,11 +24,10 @@
import com.google.common.util.concurrent.ListenableFuture;
-import org.junit.Ignore;
-@Ignore("TODO(b/177266929): Enable this test once schema migration is implemented")
-public class AppSearchSchemaMigrationCtsTest extends AppSearchSchemaMigrationCtsTestBase {
- @Override
+// TODO(b/177266929): Enable this test once schema migration is implemented
+public class AppSearchSchemaMigrationCtsTest /* extends AppSearchSchemaMigrationCtsTestBase */ {
+ // @Override
protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName) {
return AppSearchSessionShimImpl.createSearchSession(
new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build());
diff --git a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
index c5f83c3..49c5446 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
@@ -31,6 +31,7 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
+import android.platform.test.annotations.AppModeFull;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
@@ -53,6 +54,7 @@
* This doesn't extend the {@link android.app.appsearch.cts.GlobalSearchSessionCtsTestBase} since
* these test cases can't be run in a non-platform environment.
*/
+@AppModeFull(reason = "Can't bind to helper apps from instant mode")
public class GlobalSearchSessionPlatformCtsTest {
private static final long TIMEOUT_BIND_SERVICE_SEC = 2;
@@ -147,14 +149,14 @@
@Test
public void testNoPackageAccess_wrongPackageName() throws Exception {
mDb.setSchema(
- new SetSchemaRequest.Builder()
- .addSchemas(AppSearchEmail.SCHEMA)
- .setSchemaTypeVisibilityForPackage(
- AppSearchEmail.SCHEMA_TYPE,
- /*visible=*/ true,
- new PackageIdentifier(
- "some.other.package", PKG_A_CERT_SHA256))
- .build())
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchEmail.SCHEMA_TYPE,
+ /*visible=*/ true,
+ new PackageIdentifier(
+ "some.other.package", PKG_A_CERT_SHA256))
+ .build())
.get();
checkIsBatchResultSuccess(
mDb.put(
@@ -168,13 +170,13 @@
@Test
public void testNoPackageAccess_wrongCertificate() throws Exception {
mDb.setSchema(
- new SetSchemaRequest.Builder()
- .addSchemas(AppSearchEmail.SCHEMA)
- .setSchemaTypeVisibilityForPackage(
- AppSearchEmail.SCHEMA_TYPE,
- /*visible=*/ true,
- new PackageIdentifier(PKG_A, new byte[] {10}))
- .build())
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchEmail.SCHEMA_TYPE,
+ /*visible=*/ true,
+ new PackageIdentifier(PKG_A, new byte[] {10}))
+ .build())
.get();
checkIsBatchResultSuccess(
mDb.put(
@@ -188,13 +190,13 @@
@Test
public void testAllowPackageAccess() throws Exception {
mDb.setSchema(
- new SetSchemaRequest.Builder()
- .addSchemas(AppSearchEmail.SCHEMA)
- .setSchemaTypeVisibilityForPackage(
- AppSearchEmail.SCHEMA_TYPE,
- /*visible=*/ true,
- new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
- .build())
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchEmail.SCHEMA_TYPE,
+ /*visible=*/ true,
+ new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+ .build())
.get();
checkIsBatchResultSuccess(
mDb.put(
@@ -209,17 +211,17 @@
@Test
public void testAllowMultiplePackageAccess() throws Exception {
mDb.setSchema(
- new SetSchemaRequest.Builder()
- .addSchemas(AppSearchEmail.SCHEMA)
- .setSchemaTypeVisibilityForPackage(
- AppSearchEmail.SCHEMA_TYPE,
- /*visible=*/ true,
- new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
- .setSchemaTypeVisibilityForPackage(
- AppSearchEmail.SCHEMA_TYPE,
- /*visible=*/ true,
- new PackageIdentifier(PKG_B, PKG_B_CERT_SHA256))
- .build())
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchEmail.SCHEMA_TYPE,
+ /*visible=*/ true,
+ new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchEmail.SCHEMA_TYPE,
+ /*visible=*/ true,
+ new PackageIdentifier(PKG_B, PKG_B_CERT_SHA256))
+ .build())
.get();
checkIsBatchResultSuccess(
mDb.put(
@@ -234,13 +236,13 @@
@Test
public void testNoPackageAccess_revoked() throws Exception {
mDb.setSchema(
- new SetSchemaRequest.Builder()
- .addSchemas(AppSearchEmail.SCHEMA)
- .setSchemaTypeVisibilityForPackage(
- AppSearchEmail.SCHEMA_TYPE,
- /*visible=*/ true,
- new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
- .build())
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchEmail.SCHEMA_TYPE,
+ /*visible=*/ true,
+ new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+ .build())
.get();
checkIsBatchResultSuccess(
mDb.put(
@@ -251,13 +253,13 @@
// Set the schema again, but package access as false.
mDb.setSchema(
- new SetSchemaRequest.Builder()
- .addSchemas(AppSearchEmail.SCHEMA)
- .setSchemaTypeVisibilityForPackage(
- AppSearchEmail.SCHEMA_TYPE,
- /*visible=*/ false,
- new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
- .build())
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchEmail.SCHEMA_TYPE,
+ /*visible=*/ false,
+ new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+ .build())
.get();
checkIsBatchResultSuccess(
mDb.put(
@@ -268,13 +270,13 @@
// Set the schema again, but with default (i.e. no) access
mDb.setSchema(
- new SetSchemaRequest.Builder()
- .addSchemas(AppSearchEmail.SCHEMA)
- .setSchemaTypeVisibilityForPackage(
- AppSearchEmail.SCHEMA_TYPE,
- /*visible=*/ false,
- new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
- .build())
+ new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchEmail.SCHEMA)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchEmail.SCHEMA_TYPE,
+ /*visible=*/ false,
+ new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+ .build())
.get();
checkIsBatchResultSuccess(
mDb.put(
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index 5dd248c..0cff3d2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -131,6 +131,7 @@
MultiWindowEmptyActivity.expectNewInstance(true);
splitWindow(mActivity);
+ mUiBot.waitForIdleSync();
MultiWindowLoginActivity loginActivity = MultiWindowLoginActivity.waitNewInstance();
amStartActivity(MultiWindowEmptyActivity.class);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java
index 4b68250..4c778a4 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java
@@ -91,6 +91,26 @@
}
}
+ private void verifySupportedSizes(CameraExtensionCharacteristics chars, String cameraId,
+ Integer extension, int format) throws Exception {
+ List<Size> extensionSizes = chars.getExtensionSupportedSizes(extension, format);
+ assertFalse(String.format("No available sizes for extension %d on camera id: %s " +
+ "using format: %x", extension, cameraId, format), extensionSizes.isEmpty());
+ try {
+ openDevice(cameraId);
+ List<Size> cameraSizes = Arrays.asList(
+ mTestRule.getStaticInfo().getAvailableSizesForFormatChecked(format,
+ StaticMetadata.StreamDirection.Output));
+ for (Size extensionSize : extensionSizes) {
+ assertTrue(String.format("Supported extension %d on camera id: %s advertises " +
+ " resolution %s unsupported by camera", extension, cameraId,
+ extensionSize), cameraSizes.contains(extensionSize));
+ }
+ } finally {
+ mTestRule.closeDevice(cameraId);
+ }
+ }
+
private <T> void verifyUnsupportedExtension(CameraExtensionCharacteristics chars,
Integer extension, Class<T> klass) {
try {
@@ -138,6 +158,7 @@
List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
for (Integer extension : supportedExtensions) {
verifySupportedSizes(extensionChars, id, extension, SurfaceTexture.class);
+ verifySupportedSizes(extensionChars, id, extension, ImageFormat.JPEG);
}
}
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
index ae0f5d8..fc1bd8d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
@@ -16,7 +16,6 @@
package android.hardware.camera2.cts;
-import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
@@ -38,11 +37,14 @@
import android.hardware.camera2.CameraExtensionCharacteristics;
import android.hardware.camera2.CameraExtensionSession;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestRule;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
+import android.media.ExifInterface;
import android.media.Image;
import android.media.ImageReader;
import android.util.Size;
@@ -61,6 +63,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -467,6 +470,10 @@
@Test
public void testMultiFrameCapture() throws Exception {
final int IMAGE_COUNT = 10;
+ final int SUPPORTED_CAPTURE_OUTPUT_FORMATS[] = {
+ ImageFormat.YUV_420_888,
+ ImageFormat.JPEG
+ };
for (String id : mCameraIdsUnderTest) {
StaticMetadata staticMeta =
new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
@@ -478,85 +485,94 @@
mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
for (Integer extension : supportedExtensions) {
- int captureFormat = ImageFormat.YUV_420_888;
- List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
- captureFormat);
- if (extensionSizes.isEmpty()) {
- captureFormat = ImageFormat.JPEG;
- extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+ for (int captureFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
captureFormat);
- }
- Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
- SimpleImageReaderListener imageListener = new SimpleImageReaderListener(false, 1);
- ImageReader extensionImageReader = CameraTestUtils.makeImageReader(maxSize,
- captureFormat, /*maxImages*/ 1, imageListener,
- mTestRule.getHandler());
- Surface imageReaderSurface = extensionImageReader.getSurface();
- OutputConfiguration readerOutput = new OutputConfiguration(
- OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
- List<OutputConfiguration> outputConfigs = new ArrayList<>();
- outputConfigs.add(readerOutput);
-
- BlockingExtensionSessionCallback sessionListener =
- new BlockingExtensionSessionCallback(mock(
- CameraExtensionSession.StateCallback.class));
- ExtensionSessionConfiguration configuration =
- new ExtensionSessionConfiguration(extension, outputConfigs,
- new HandlerExecutor(mTestRule.getHandler()),
- sessionListener);
-
- try {
- mTestRule.openDevice(id);
- CameraDevice camera = mTestRule.getCamera();
- camera.createExtensionSession(configuration);
- CameraExtensionSession extensionSession =
- sessionListener.waitAndGetSession(
- SESSION_CONFIGURE_TIMEOUT_MS);
- assertNotNull(extensionSession);
-
- CaptureRequest.Builder captureBuilder =
- mTestRule.getCamera().createCaptureRequest(
- android.hardware.camera2.CameraDevice.TEMPLATE_STILL_CAPTURE);
- captureBuilder.addTarget(imageReaderSurface);
- CameraExtensionSession.ExtensionCaptureCallback captureCallback =
- mock(CameraExtensionSession.ExtensionCaptureCallback.class);
-
- for (int i = 0; i < IMAGE_COUNT; i++) {
- CaptureRequest request = captureBuilder.build();
- int sequenceId = extensionSession.capture(request,
- new HandlerExecutor(mTestRule.getHandler()), captureCallback);
-
- Image img =
- imageListener.getImage(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS);
- validateImage(img, maxSize.getWidth(), maxSize.getHeight(),
- captureFormat, null);
- img.close();
-
- verify(captureCallback, times(1))
- .onCaptureStarted(eq(extensionSession), eq(request), anyLong());
- verify(captureCallback,
- timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
- .onCaptureProcessStarted(extensionSession, request);
- verify(captureCallback,
- timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
- .onCaptureSequenceCompleted(extensionSession, sequenceId);
+ if (extensionSizes.isEmpty()) {
+ continue;
}
+ Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener(false,
+ 1);
+ ImageReader extensionImageReader = CameraTestUtils.makeImageReader(maxSize,
+ captureFormat, /*maxImages*/ 1, imageListener,
+ mTestRule.getHandler());
+ Surface imageReaderSurface = extensionImageReader.getSurface();
+ OutputConfiguration readerOutput = new OutputConfiguration(
+ OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
+ List<OutputConfiguration> outputConfigs = new ArrayList<>();
+ outputConfigs.add(readerOutput);
- verify(captureCallback, times(0))
- .onCaptureSequenceAborted(any(CameraExtensionSession.class),
- anyInt());
- verify(captureCallback, times(0))
- .onCaptureFailed(any(CameraExtensionSession.class),
- any(CaptureRequest.class));
+ BlockingExtensionSessionCallback sessionListener =
+ new BlockingExtensionSessionCallback(mock(
+ CameraExtensionSession.StateCallback.class));
+ ExtensionSessionConfiguration configuration =
+ new ExtensionSessionConfiguration(extension, outputConfigs,
+ new HandlerExecutor(mTestRule.getHandler()),
+ sessionListener);
- extensionSession.close();
+ try {
+ mTestRule.openDevice(id);
+ CameraDevice camera = mTestRule.getCamera();
+ camera.createExtensionSession(configuration);
+ CameraExtensionSession extensionSession =
+ sessionListener.waitAndGetSession(
+ SESSION_CONFIGURE_TIMEOUT_MS);
+ assertNotNull(extensionSession);
- sessionListener.getStateWaiter().waitForState(
- BlockingExtensionSessionCallback.SESSION_CLOSED,
- SESSION_CLOSE_TIMEOUT_MS);
- } finally {
- mTestRule.closeDevice(id);
- extensionImageReader.close();
+ CaptureRequest.Builder captureBuilder =
+ mTestRule.getCamera().createCaptureRequest(
+ CameraDevice.TEMPLATE_STILL_CAPTURE);
+ captureBuilder.addTarget(imageReaderSurface);
+ CameraExtensionSession.ExtensionCaptureCallback captureCallback =
+ mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+
+ for (int i = 0; i < IMAGE_COUNT; i++) {
+ int jpegOrientation = (i * 90) % 360; // degrees [0..270]
+ if (captureFormat == ImageFormat.JPEG) {
+ captureBuilder.set(CaptureRequest.JPEG_ORIENTATION,
+ jpegOrientation);
+ }
+ CaptureRequest request = captureBuilder.build();
+ int sequenceId = extensionSession.capture(request,
+ new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+
+ Image img =
+ imageListener.getImage(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS);
+ if (captureFormat == ImageFormat.JPEG) {
+ verifyJpegOrientation(img, maxSize, jpegOrientation);
+ } else {
+ validateImage(img, maxSize.getWidth(), maxSize.getHeight(),
+ captureFormat, null);
+ }
+ img.close();
+
+ verify(captureCallback, times(1))
+ .onCaptureStarted(eq(extensionSession), eq(request), anyLong());
+ verify(captureCallback,
+ timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+ .onCaptureProcessStarted(extensionSession, request);
+ verify(captureCallback,
+ timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+ .onCaptureSequenceCompleted(extensionSession, sequenceId);
+ }
+
+ verify(captureCallback, times(0))
+ .onCaptureSequenceAborted(any(CameraExtensionSession.class),
+ anyInt());
+ verify(captureCallback, times(0))
+ .onCaptureFailed(any(CameraExtensionSession.class),
+ any(CaptureRequest.class));
+
+ extensionSession.close();
+
+ sessionListener.getStateWaiter().waitForState(
+ BlockingExtensionSessionCallback.SESSION_CLOSED,
+ SESSION_CLOSE_TIMEOUT_MS);
+ } finally {
+ mTestRule.closeDevice(id);
+ extensionImageReader.close();
+ }
}
}
}
@@ -577,14 +593,10 @@
mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
for (Integer extension : supportedExtensions) {
- int captureFormat = ImageFormat.YUV_420_888;
+ int captureFormat = ImageFormat.JPEG;
List<Size> captureSizes = extensionChars.getExtensionSupportedSizes(extension,
captureFormat);
- if (captureSizes.isEmpty()) {
- captureFormat = ImageFormat.JPEG;
- captureSizes = extensionChars.getExtensionSupportedSizes(extension,
- captureFormat);
- }
+ assertFalse("No Jpeg output supported", captureSizes.isEmpty());
Size captureMaxSize =
CameraTestUtils.getMaxSize(captureSizes.toArray(new Size[0]));
@@ -737,6 +749,62 @@
}
}
+ private void verifyJpegOrientation(Image img, Size jpegSize, int requestedOrientation)
+ throws IOException {
+ byte[] blobBuffer = getDataFromImage(img);
+ String blobFilename = mTestRule.getDebugFileNameBase() + "/verifyJpegKeys.jpeg";
+ dumpFile(blobFilename, blobBuffer);
+ ExifInterface exif = new ExifInterface(blobFilename);
+ int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, /*defaultValue*/0);
+ int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, /*defaultValue*/0);
+ Size exifSize = new Size(exifWidth, exifHeight);
+ int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+ /*defaultValue*/ ExifInterface.ORIENTATION_UNDEFINED);
+ final int ORIENTATION_MIN = ExifInterface.ORIENTATION_UNDEFINED;
+ final int ORIENTATION_MAX = ExifInterface.ORIENTATION_ROTATE_270;
+ assertTrue(String.format("Exif orientation must be in range of [%d, %d]",
+ ORIENTATION_MIN, ORIENTATION_MAX),
+ exifOrientation >= ORIENTATION_MIN && exifOrientation <= ORIENTATION_MAX);
+
+ /**
+ * Device captured image doesn't respect the requested orientation,
+ * which means it rotates the image buffer physically. Then we
+ * should swap the exif width/height accordingly to compare.
+ */
+ boolean deviceRotatedImage = exifOrientation == ExifInterface.ORIENTATION_UNDEFINED;
+
+ if (deviceRotatedImage) {
+ // Case 1.
+ boolean needSwap = (requestedOrientation % 180 == 90);
+ if (needSwap) {
+ exifSize = new Size(exifHeight, exifWidth);
+ }
+ } else {
+ // Case 2.
+ assertEquals("Exif orientation should match requested orientation",
+ requestedOrientation, getExifOrientationInDegree(exifOrientation));
+ }
+
+ assertEquals("Exif size should match jpeg capture size", jpegSize, exifSize);
+ }
+
+ private static int getExifOrientationInDegree(int exifOrientation) {
+ switch (exifOrientation) {
+ case ExifInterface.ORIENTATION_NORMAL:
+ return 0;
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ return 90;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ return 180;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ return 270;
+ default:
+ fail("It is impossible to get non 0, 90, 180, 270 degress exif" +
+ "info based on the request orientation range");
+ return -1;
+ }
+ }
+
public static class SimpleCaptureCallback
extends CameraExtensionSession.ExtensionCaptureCallback {
private long mLastTimestamp = -1;
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
index 2e5bcd5..1ad21cc 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -1064,9 +1064,22 @@
Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
StaticMetadata.StreamDirection.Output);
+ boolean secureTest = setUsageFlag &&
+ ((usageFlag & HardwareBuffer.USAGE_PROTECTED_CONTENT) != 0);
+ Size secureDataSize = null;
+ if (secureTest) {
+ secureDataSize = mStaticInfo.getCharacteristics().get(
+ CameraCharacteristics.SCALER_DEFAULT_SECURE_IMAGE_SIZE);
+ }
+
// for each resolution, test imageReader:
for (Size sz : availableSizes) {
try {
+ // For secure mode test only test default secure data size if HAL advertises one.
+ if (secureDataSize != null && !secureDataSize.equals(sz)) {
+ continue;
+ }
+
if (VERBOSE) {
Log.v(TAG, "Testing size " + sz.toString() + " format " + format
+ " for camera " + mCamera.getId());
diff --git a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
index a6c477c..14303c8 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -435,7 +435,13 @@
// Tested in ExtendedCameraCharacteristicsTest
return;
case REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA:
- // No other restrictions with other metadata keys which are reliably testable.
+ if (!isCapabilityAvailable) {
+ mCollector.expectTrue(
+ "SCALER_DEFAULT_SECURE_IMAGE_SIZE must not present if the device" +
+ "does not support SECURE_IMAGE_DATA capability",
+ !mStaticInfo.areKeysAvailable(
+ CameraCharacteristics.SCALER_DEFAULT_SECURE_IMAGE_SIZE));
+ }
return;
default:
capabilityName = "Unknown";
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
index 394f346..713d132 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
@@ -100,6 +100,10 @@
mContext = context;
}
+ public String getDebugFileNameBase() {
+ return mDebugFileNameBase;
+ }
+
public Context getContext() {
return mContext;
}
diff --git a/tests/devicepolicy/AndroidTest.xml b/tests/devicepolicy/AndroidTest.xml
index 534ce53..2cff288 100644
--- a/tests/devicepolicy/AndroidTest.xml
+++ b/tests/devicepolicy/AndroidTest.xml
@@ -28,8 +28,8 @@
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.devicepolicy.cts" />
- <option name="exclude-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
- <option name="exclude-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
<option name="hidden-api-checks" value="false" />
</test>
</configuration>
\ No newline at end of file
diff --git a/tests/devicepolicy/DevicePolicySecondaryUserTest.xml b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
index 5fa6f0b..798f52e 100644
--- a/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
+++ b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
@@ -32,7 +32,7 @@
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.devicepolicy.cts" />
- <option name="include-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser" />
+ <option name="include-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
<!-- <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
<option name="hidden-api-checks" value="false" />
</test>
diff --git a/tests/devicepolicy/DevicePolicyWorkProfileTest.xml b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
index aa882be..1e45241 100644
--- a/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
+++ b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
@@ -31,7 +31,7 @@
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.devicepolicy.cts" />
- <option name="include-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
+ <option name="include-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
<!-- <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
<option name="hidden-api-checks" value="false" />
</test>
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
index a80a32b..e2cd1db 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
@@ -16,7 +16,7 @@
package android.devicepolicy.cts;
-import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.PRIMARY_USER;
+import static com.android.bedstead.harrier.DeviceState.UserType.PRIMARY_USER;
import static com.google.common.truth.Truth.assertThat;
@@ -39,13 +39,13 @@
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
-import com.android.compatibility.common.util.enterprise.DeviceState;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasSecondaryUser;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasWorkProfile;
-import com.android.compatibility.common.util.enterprise.annotations.Postsubmit;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.Postsubmit;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
import org.junit.ClassRule;
import org.junit.Ignore;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
index 14ed51d..e4eb89d 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
@@ -40,9 +40,9 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
import com.android.bedstead.harrier.annotations.RequireFeatures;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
import org.junit.ClassRule;
import org.junit.Rule;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java b/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java
index 6d8df49..46cf1ec 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java
@@ -23,13 +23,10 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.os.Process;
-import android.os.UserHandle;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
-
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
index e75e529..53b58e5 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
@@ -26,7 +26,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.DeviceState;
import com.android.bedstead.harrier.annotations.RequireFeatures;
import org.junit.ClassRule;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java
index 2b7679d..bae83f6 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java
@@ -19,19 +19,14 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertThrows;
import android.app.ActivityManager;
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.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
@@ -40,11 +35,14 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.enterprise.DeviceState;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasSecondaryUser;
-import com.android.compatibility.common.util.enterprise.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.Postsubmit;
import com.android.bedstead.harrier.annotations.RequireFeatures;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.nene.TestApis;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
import org.junit.After;
import org.junit.ClassRule;
@@ -52,213 +50,201 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.util.NoSuchElementException;
-import java.util.Scanner;
+import java.util.function.Function;
@RunWith(AndroidJUnit4.class)
public final class StartProfilesTest {
- private static final int USER_START_TIMEOUT_MILLIS = 30 * 1000; // 30 seconds
+ // We set this to 30 seconds because if the total test time goes over 66 seconds then it causes
+ // infrastructure problems
+ private static final long PROFILE_ACCESSIBLE_BROADCAST_TIMEOUT = 30 * 1000;
- private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
- private static final UserManager USER_MANAGER = CONTEXT.getSystemService(UserManager.class);
+ private static final Context sContext = ApplicationProvider.getApplicationContext();
+ private static final UserManager sUserManager = sContext.getSystemService(UserManager.class);
+ private static final ActivityManager sActivityManager =
+ sContext.getSystemService(ActivityManager.class);
- private BroadcastReceiver mBroadcastReceiver;
private UiAutomation mUiAutomation =
InstrumentationRegistry.getInstrumentation().getUiAutomation();
- private final Object mUserStartStopLock = new Object();
- private boolean mBroadcastReceived = false;
+ private final TestApis mTestApis = new TestApis();
@ClassRule @Rule
- public static final DeviceState DEVICE_STATE = new DeviceState();
+ public static final DeviceState sDeviceState = new DeviceState();
@After
public void tearDown() {
mUiAutomation.dropShellPermissionIdentity();
}
- //TODO: b/171565394 - use DEVICE_STATE.registerBroadcastReceiver when it supports extra
- // filters (EXTRA_USER)
- private void registerBroadcastReceiver(final UserHandle userHandle) {
- mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()) {
- case Intent.ACTION_PROFILE_ACCESSIBLE:
- case Intent.ACTION_PROFILE_INACCESSIBLE:
- if (userHandle.equals(intent.getParcelableExtra(Intent.EXTRA_USER))) {
- synchronized (mUserStartStopLock) {
- mBroadcastReceived = true;
- mUserStartStopLock.notifyAll();
- }
- }
- break;
- }
- }
- };
-
- IntentFilter filter = new IntentFilter(Intent.ACTION_PROFILE_ACCESSIBLE);
- filter.addAction(Intent.ACTION_PROFILE_INACCESSIBLE);
- CONTEXT.registerReceiver(mBroadcastReceiver, filter);
- }
-
- @Test
- //TODO: b/171565394 - remove after infra. updates
- @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
- @RequireRunOnPrimaryUser
- @EnsureHasWorkProfile
- public void testStartProfile() throws InterruptedException {
- mUiAutomation.adoptShellPermissionIdentity(
- "android.permission.INTERACT_ACROSS_USERS_FULL",
- "android.permission.INTERACT_ACROSS_USERS",
- "android.permission.CREATE_USERS");
-
- UserHandle workProfile = DEVICE_STATE.getWorkProfile();
- registerBroadcastReceiver(workProfile);
-
- synchronized (mUserStartStopLock) {
- mUiAutomation.executeShellCommand("am stop-user -f " + workProfile.getIdentifier());
- mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
- }
- assertThat(USER_MANAGER.isUserRunning(workProfile)).isFalse();
-
- final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
- synchronized (mUserStartStopLock) {
- assertThat(activityManager.startProfile(workProfile)).isTrue();
- mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
- }
-
- assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
-
- CONTEXT.unregisterReceiver(mBroadcastReceiver);
+ private Function<Intent, Boolean> userIsEqual(UserHandle userHandle) {
+ return (intent) -> userHandle.equals(intent.getParcelableExtra(Intent.EXTRA_USER));
}
@Test
@RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
@RequireRunOnPrimaryUser
@EnsureHasWorkProfile
- public void testStopProfile() throws InterruptedException {
- //TODO: b/171565394 - remove after infra supports shell permissions annotation
+ public void startProfile_returnsTrue() {
mUiAutomation.adoptShellPermissionIdentity(
"android.permission.INTERACT_ACROSS_USERS_FULL",
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.CREATE_USERS");
+ mTestApis.users().find(sDeviceState.getWorkProfile()).stop();
- UserHandle workProfile = DEVICE_STATE.getWorkProfile();
- registerBroadcastReceiver(workProfile);
-
- //TODO: b/171565394 - remove after infra. guarantees users are started
- assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
-
- final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
- synchronized (mUserStartStopLock) {
- assertThat(activityManager.stopProfile(workProfile)).isTrue();
- mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
- }
-
- assertThat(USER_MANAGER.isUserRunning(workProfile)).isFalse();
-
- CONTEXT.unregisterReceiver(mBroadcastReceiver);
-
- //TODO: b/171565394 - move/remove this when DeviceState impl. state restore (reusing users)
- //restore started state
- runCommandWithOutput("am start-user -w " + workProfile.getIdentifier());
+ assertThat(sActivityManager.startProfile(sDeviceState.getWorkProfile())).isTrue();
}
@Test
@RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
@RequireRunOnPrimaryUser
@EnsureHasWorkProfile
- public void testStopAndRestartProfile() throws InterruptedException {
- //TODO: b/171565394 - remove after infra supports shell permissions annotation
+ public void startProfile_broadcastIsReceived_profileIsStarted() {
mUiAutomation.adoptShellPermissionIdentity(
"android.permission.INTERACT_ACROSS_USERS_FULL",
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.CREATE_USERS");
+ mTestApis.users().find(sDeviceState.getWorkProfile()).stop();
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ Intent.ACTION_PROFILE_ACCESSIBLE, userIsEqual(sDeviceState.getWorkProfile()));
+ sActivityManager.startProfile(sDeviceState.getWorkProfile());
- UserHandle workProfile = DEVICE_STATE.getWorkProfile();
- registerBroadcastReceiver(workProfile);
+ broadcastReceiver.awaitForBroadcastOrFail();
- //TODO: b/171565394 - remove after infra. guarantees users are started
- assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
-
- final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
- synchronized (mUserStartStopLock) {
- assertThat(activityManager.stopProfile(workProfile)).isTrue();
- mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
- }
- // start profile as soon as ACTION_PROFILE_INACCESSIBLE is received
- // verify that ACTION_PROFILE_ACCESSIBLE is received if profile is re-started
- mBroadcastReceived = false;
- synchronized (mUserStartStopLock) {
- assertThat(activityManager.startProfile(workProfile)).isTrue();
- mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
- }
-
- assertWithMessage("Expected to receive ACTION_PROFILE_ACCESSIBLE broadcast").that(
- mBroadcastReceived).isTrue();
- assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
-
- CONTEXT.unregisterReceiver(mBroadcastReceiver);
-
- //TODO: b/171565394 - move/remove this when DeviceState impl. state restore (reusing users)
- //restore started state
- runCommandWithOutput("am start-user -w " + workProfile.getIdentifier());
+ assertThat(sUserManager.isUserRunning(sDeviceState.getWorkProfile())).isTrue();
}
@Test
@RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
@RequireRunOnPrimaryUser
@EnsureHasWorkProfile
- public void testStopAndRestartProfile_dontWaitForBroadcast() throws InterruptedException {
- //TODO: b/171565394 - remove after infra supports shell permissions annotation
+ public void stopProfile_returnsTrue() {
+ // TODO(b/171565394): remove after infra supports shell permissions annotation
mUiAutomation.adoptShellPermissionIdentity(
"android.permission.INTERACT_ACROSS_USERS_FULL",
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.CREATE_USERS");
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
- UserHandle workProfile = DEVICE_STATE.getWorkProfile();
- registerBroadcastReceiver(workProfile);
+ try {
+ assertThat(sActivityManager.stopProfile(sDeviceState.getWorkProfile())).isTrue();
+ } finally {
+ // TODO(b/171565394): Remove once teardown is done for us
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+ }
+ }
- //TODO: b/171565394 - remove after infra. guarantees users are started
- assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
+ @Test
+ @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireRunOnPrimaryUser
+ @EnsureHasWorkProfile
+ public void stopProfile_profileIsStopped() {
+ // TODO(b/171565394): remove after infra supports shell permissions annotation
+ mUiAutomation.adoptShellPermissionIdentity(
+ "android.permission.INTERACT_ACROSS_USERS_FULL",
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.CREATE_USERS");
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ Intent.ACTION_PROFILE_INACCESSIBLE, userIsEqual(sDeviceState.getWorkProfile()));
- final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+ try {
+ sActivityManager.stopProfile(sDeviceState.getWorkProfile());
+ broadcastReceiver.awaitForBroadcastOrFail();
+
+ assertThat(sUserManager.isUserRunning(sDeviceState.getWorkProfile())).isFalse();
+ } finally {
+ // TODO(b/171565394): Remove once teardown is done for us
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+ }
+ }
+
+ @Test
+ @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireRunOnPrimaryUser
+ @EnsureHasWorkProfile
+ public void startUser_immediatelyAfterStopped_profileIsStarted() {
+ // TODO(b/171565394): remove after infra supports shell permissions annotation
+ mUiAutomation.adoptShellPermissionIdentity(
+ "android.permission.INTERACT_ACROSS_USERS_FULL",
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.CREATE_USERS");
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ Intent.ACTION_PROFILE_INACCESSIBLE, userIsEqual(sDeviceState.getWorkProfile()));
+
+ sActivityManager.stopProfile(sDeviceState.getWorkProfile());
+ broadcastReceiver.awaitForBroadcast();
+
+ try {
+ // start profile as soon as ACTION_PROFILE_INACCESSIBLE is received
+ // verify that ACTION_PROFILE_ACCESSIBLE is received if profile is re-started
+ broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ Intent.ACTION_PROFILE_ACCESSIBLE, userIsEqual(sDeviceState.getWorkProfile()));
+ sActivityManager.startProfile(sDeviceState.getWorkProfile());
+ Intent broadcast = broadcastReceiver.awaitForBroadcast();
+
+ assertWithMessage("Expected to receive ACTION_PROFILE_ACCESSIBLE broadcast").that(
+ broadcast).isNotNull();
+ assertThat(sUserManager.isUserRunning(sDeviceState.getWorkProfile())).isTrue();
+ } finally {
+ // TODO(b/171565394): Remove once teardown is done for us
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+ }
+ }
+
+ @Test
+ @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireRunOnPrimaryUser
+ @EnsureHasWorkProfile
+ public void startUser_userIsStopping_profileIsStarted() {
+ // TODO(b/171565394): remove after infra supports shell permissions annotation
+ mUiAutomation.adoptShellPermissionIdentity(
+ "android.permission.INTERACT_ACROSS_USERS_FULL",
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.CREATE_USERS");
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+
+ // stop and restart profile without waiting for ACTION_PROFILE_INACCESSIBLE broadcast
+ sActivityManager.stopProfile(sDeviceState.getWorkProfile());
+ try {
+ sActivityManager.startProfile(sDeviceState.getWorkProfile());
+
+ assertThat(sUserManager.isUserRunning(sDeviceState.getWorkProfile())).isTrue();
+ } finally {
+ // TODO(b/171565394): Remove once teardown is done for us
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+ }
+ }
+
+ @Test
+ @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireRunOnPrimaryUser
+ @EnsureHasWorkProfile
+ @Postsubmit(reason="Slow test due to validating a broadcast isn't received")
+ public void startUser_userIsStopping_noBroadcastIsReceived() {
+ // TODO(b/171565394): remove after infra supports shell permissions annotation
+ mUiAutomation.adoptShellPermissionIdentity(
+ "android.permission.INTERACT_ACROSS_USERS_FULL",
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.CREATE_USERS");
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
+
// stop and restart profile without waiting for ACTION_PROFILE_INACCESSIBLE broadcast
// ACTION_PROFILE_ACCESSIBLE should not be received as profile was not fully stopped before
// restarting
- assertThat(activityManager.stopProfile(workProfile)).isTrue();
- mBroadcastReceived = false;
- synchronized (mUserStartStopLock) {
- assertThat(activityManager.startProfile(workProfile)).isTrue();
- mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
- }
-
- assertWithMessage("Should have not received ACTION_PROFILE_ACCESSIBLE broadcast").that(
- mBroadcastReceived).isFalse();
- assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
-
- CONTEXT.unregisterReceiver(mBroadcastReceiver);
-
- //TODO: b/171565394 - move/remove this when DeviceState impl. state restore (reusing users)
- //restore started state
- runCommandWithOutput("am start-user -w " + workProfile.getIdentifier());
- }
-
- @Test
- @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
- @RequireRunOnPrimaryUser
- @EnsureHasWorkProfile
- public void testStartProfileWithoutPermission_throwsException() {
- UserHandle workProfile = DEVICE_STATE.getWorkProfile();
-
- final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+ sActivityManager.stopProfile(sDeviceState.getWorkProfile());
try {
- activityManager.startProfile(workProfile);
- fail("Should have received an exception");
- } catch (SecurityException expected) {
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ Intent.ACTION_PROFILE_ACCESSIBLE, userIsEqual(sDeviceState.getWorkProfile()));
+ sActivityManager.startProfile(sDeviceState.getWorkProfile());
+
+ Intent broadcast =
+ broadcastReceiver.awaitForBroadcast(PROFILE_ACCESSIBLE_BROADCAST_TIMEOUT);
+ assertWithMessage("Expected not to receive ACTION_PROFILE_ACCESSIBLE broadcast").that(
+ broadcast).isNull();
+ } finally {
+ // TODO(b/171565394): Remove once teardown is done for us
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
}
}
@@ -266,140 +252,112 @@
@RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
@RequireRunOnPrimaryUser
@EnsureHasWorkProfile
- public void testStopProfileWithoutPermission_throwsException() {
- UserHandle workProfile = DEVICE_STATE.getWorkProfile();
+ public void startProfile_withoutPermission_throwsException() {
+ assertThrows(SecurityException.class,
+ () -> sActivityManager.startProfile(sDeviceState.getWorkProfile()));
+ }
- final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+ @Test
+ @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireRunOnPrimaryUser
+ @EnsureHasWorkProfile
+ public void stopProfile_withoutPermission_throwsException() {
try {
- activityManager.stopProfile(workProfile);
- fail("Should have received an exception");
- } catch (SecurityException expected) {
+ assertThrows(SecurityException.class,
+ () -> sActivityManager.stopProfile(sDeviceState.getWorkProfile()));
+ } finally {
+ // TODO(b/171565394): Remove once teardown is done for us
+ mTestApis.users().find(sDeviceState.getWorkProfile()).start();
}
}
@Test
@RequireRunOnPrimaryUser
@EnsureHasSecondaryUser
- public void testStartFullUserAsProfile_throwsException() {
+ public void startProfile_startingFullUser_throwsException() {
mUiAutomation.adoptShellPermissionIdentity(
"android.permission.INTERACT_ACROSS_USERS_FULL",
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.CREATE_USERS");
- UserHandle secondaryUser = DEVICE_STATE.getSecondaryUser();
-
- final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
- try {
- activityManager.startProfile(secondaryUser);
- fail("Should have received an exception");
- } catch (IllegalArgumentException expected) {
- }
+ assertThrows(IllegalArgumentException.class,
+ () -> sActivityManager.startProfile(sDeviceState.getSecondaryUser()));
}
@Test
@RequireRunOnPrimaryUser
@EnsureHasSecondaryUser
- public void testStopFullUserAsProfile_throwsException() {
+ public void stopProfile_stoppingFullUser_throwsException() {
mUiAutomation.adoptShellPermissionIdentity(
"android.permission.INTERACT_ACROSS_USERS_FULL",
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.CREATE_USERS");
- UserHandle secondaryUser = DEVICE_STATE.getSecondaryUser();
-
- final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
try {
- activityManager.stopProfile(secondaryUser);
- fail("Should have received an exception");
- } catch (IllegalArgumentException expected) {
+ assertThrows(IllegalArgumentException.class,
+ () -> sActivityManager.stopProfile(sDeviceState.getSecondaryUser()));
+ } finally {
+ // TODO(b/171565394): Remove once teardown is done for us
+ mTestApis.users().find(sDeviceState.getSecondaryUser()).start();
}
}
@Test
@RequireRunOnPrimaryUser
- public void testStartTvProfile() throws InterruptedException {
+ public void startProfile_tvProfile_profileIsStarted() {
mUiAutomation.adoptShellPermissionIdentity(
"android.permission.INTERACT_ACROSS_USERS_FULL",
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.CREATE_USERS");
-
- //TODO: b/171565394 - migrate to new test annotation for tv profile tests
- UserHandle tvProfile = createCustomProfile("com.android.tv.profile", false);
+ // TODO(b/171565394): migrate to new test annotation for tv profile tests
+ UserHandle tvProfile = createCustomProfile("com.android.tv.profile");
assumeTrue(tvProfile != null);
+ try {
+ assertThat(sUserManager.isUserRunning(tvProfile)).isFalse();
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ Intent.ACTION_PROFILE_ACCESSIBLE, userIsEqual(tvProfile));
- registerBroadcastReceiver(tvProfile);
+ assertThat(sActivityManager.startProfile(tvProfile)).isTrue();
+ broadcastReceiver.awaitForBroadcast();
- assertThat(USER_MANAGER.isUserRunning(tvProfile)).isFalse();
-
- final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
- synchronized (mUserStartStopLock) {
- assertThat(activityManager.startProfile(tvProfile)).isTrue();
- mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+ assertThat(sUserManager.isUserRunning(tvProfile)).isTrue();
+ } finally {
+ mTestApis.users().find(tvProfile).remove();
}
-
- assertThat(USER_MANAGER.isUserRunning(tvProfile)).isTrue();
-
- CONTEXT.unregisterReceiver(mBroadcastReceiver);
-
- cleanupCustomProfile(tvProfile);
}
@Test
@RequireRunOnPrimaryUser
- public void testStopTvProfile() throws InterruptedException {
+ public void stopProfile_tvProfile_profileIsStopped() {
mUiAutomation.adoptShellPermissionIdentity(
"android.permission.INTERACT_ACROSS_USERS_FULL",
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.CREATE_USERS");
-
- //TODO: b/171565394 - migrate to new test annotation for tv profile tests
- UserHandle tvProfile = createCustomProfile("com.android.tv.profile", true);
+ // TODO(b/171565394): migrate to new test annotation for tv profile tests
+ UserHandle tvProfile = createCustomProfile("com.android.tv.profile");
assumeTrue(tvProfile != null);
+ mTestApis.users().find(tvProfile).start();
+ BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+ Intent.ACTION_PROFILE_INACCESSIBLE, userIsEqual(tvProfile));
- registerBroadcastReceiver(tvProfile);
+ try {
+ assertThat(sActivityManager.stopProfile(tvProfile)).isTrue();
+ broadcastReceiver.awaitForBroadcast();
- assertThat(USER_MANAGER.isUserRunning(tvProfile)).isTrue();
-
- final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
- synchronized (mUserStartStopLock) {
- assertThat(activityManager.stopProfile(tvProfile)).isTrue();
- mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+ assertThat(sUserManager.isUserRunning(tvProfile)).isFalse();
+ } finally {
+ mTestApis.users().find(tvProfile).remove();
}
-
- assertThat(USER_MANAGER.isUserRunning(tvProfile)).isFalse();
-
- CONTEXT.unregisterReceiver(mBroadcastReceiver);
-
- cleanupCustomProfile(tvProfile);
}
- private UserHandle createCustomProfile(String profileType, boolean startAfterCreation) {
+ private UserHandle createCustomProfile(String profileType) {
UserHandle userHandle;
try {
- userHandle = CONTEXT.getSystemService(UserManager.class).createProfile(
+ userHandle = sContext.getSystemService(UserManager.class).createProfile(
"testProfile", profileType, new ArraySet<>());
- if (startAfterCreation && userHandle != null) {
- runCommandWithOutput("am start-user -w " + userHandle.getIdentifier());
- }
} catch (NullPointerException e) {
userHandle = null;
}
return userHandle;
}
-
- private void cleanupCustomProfile(UserHandle userHandle) {
- runCommandWithOutput("pm remove-user " + userHandle.getIdentifier());
- }
-
- private String runCommandWithOutput(String command) {
- ParcelFileDescriptor p = mUiAutomation.executeShellCommand(command);
-
- InputStream inputStream = new FileInputStream(p.getFileDescriptor());
-
- try (Scanner scanner = new Scanner(inputStream, UTF_8.name())) {
- return scanner.useDelimiter("\\A").next();
- } catch (NoSuchElementException e) {
- return "";
- }
- }
}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index 4a59813..08f2f73 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -435,6 +435,9 @@
<activity android:name="android.server.wm.WindowUntrustedTouchTest$TestActivity"
android:exported="true"/>
+
+ <activity android:name="android.server.wm.DisplayHashManagerTest$TestActivity"
+ android:exported="true"/>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/framework/base/windowmanager/AndroidTest.xml b/tests/framework/base/windowmanager/AndroidTest.xml
index 2651f7d..b38c81f 100644
--- a/tests/framework/base/windowmanager/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -28,6 +28,7 @@
<option name="test-file-name" value="CtsAlertWindowService.apk"/>
<option name="test-file-name" value="CtsDeviceServicesTestApp.apk" />
<option name="test-file-name" value="CtsDeviceServicesTestApp27.apk" />
+ <option name="test-file-name" value="CtsDeviceServicesTestApp30.apk" />
<option name="test-file-name" value="CtsDeviceServicesTestSecondApp.apk" />
<option name="test-file-name" value="CtsDeviceServicesTestThirdApp.apk" />
<option name="test-file-name" value="CtsDeviceDeprecatedSdkApp.apk" />
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index 3a9d550..0acd38d 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -32,6 +32,10 @@
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:exported="true"/>
+ <activity android:name=".UiScalingTestActivity"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:exported="true"/>
<activity android:name=".TestActivityWithSameAffinity"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
diff --git a/tests/framework/base/windowmanager/app/res/layout/simple_ui_elements.xml b/tests/framework/base/windowmanager/app/res/layout/simple_ui_elements.xml
new file mode 100644
index 0000000..b5c5c71
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/layout/simple_ui_elements.xml
@@ -0,0 +1,46 @@
+<?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.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="8dp">
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Hello world!" />
+
+ <View
+ android:id="@+id/square_100dp"
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:layout_below="@+id/text"
+ android:layout_marginTop="8dp"
+ android:background="#ffc53929" />
+
+ <View
+ android:id="@+id/square_100px"
+ android:layout_width="100px"
+ android:layout_height="100px"
+ android:layout_alignTop="@+id/square_100dp"
+ android:layout_marginLeft="8dp"
+ android:layout_toRightOf="@+id/square_100dp"
+ android:background="#ff3367d6" />
+
+</RelativeLayout>
+
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 ce51a71..67a278c 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
@@ -121,6 +121,8 @@
component("SingleInstanceActivity");
public static final ComponentName HOME_ACTIVITY = component("HomeActivity");
public static final ComponentName SECONDARY_HOME_ACTIVITY = component("SecondaryHomeActivity");
+ public static final ComponentName UI_SCALING_TEST_ACTIVITY =
+ component("UiScalingTestActivity");
public static final ComponentName SINGLE_HOME_ACTIVITY = component("SingleHomeActivity");
public static final ComponentName SINGLE_SECONDARY_HOME_ACTIVITY =
component("SingleSecondaryHomeActivity");
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/UiScalingTestActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/UiScalingTestActivity.java
new file mode 100644
index 0000000..abcfb10
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/UiScalingTestActivity.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 android.server.wm.app;
+
+import android.os.Bundle;
+
+public class UiScalingTestActivity extends TestActivity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.simple_ui_elements);
+ }
+}
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp b/tests/framework/base/windowmanager/app30/Android.bp
similarity index 64%
copy from hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
copy to tests/framework/base/windowmanager/app30/Android.bp
index 72e5fef..bae3601 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
+++ b/tests/framework/base/windowmanager/app30/Android.bp
@@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,20 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-java_test_host {
- name: "CtsInstalledLoadingProgressHostTests",
- defaults: ["cts_defaults"],
- srcs: ["src/**/*.java"],
- libs: [
- "cts-tradefed",
- "tradefed",
- "compatibility-host-util",
- "cts-host-utils",
- ],
- static_libs: ["cts-install-lib-host"],
+android_test {
+ name: "CtsDeviceServicesTestApp30",
+ defaults: ["cts_support_defaults"],
+
+ static_libs: ["cts-wm-app-base"],
+
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "30",
+
test_suites: [
"cts",
"general-tests",
],
}
-
diff --git a/tests/framework/base/windowmanager/app30/AndroidManifest.xml b/tests/framework/base/windowmanager/app30/AndroidManifest.xml
new file mode 100755
index 0000000..0a8eee1
--- /dev/null
+++ b/tests/framework/base/windowmanager/app30/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.server.wm.app30">
+
+ <application android:label="App30"
+ android:debuggable="true">
+
+ <activity android:name="android.server.wm.app.TestActivity"
+ android:exported="true"
+ />
+
+ </application>
+</manifest>
diff --git a/tests/framework/base/windowmanager/app30/src/android/server/wm/app30/Components.java b/tests/framework/base/windowmanager/app30/src/android/server/wm/app30/Components.java
new file mode 100644
index 0000000..e1cc50f
--- /dev/null
+++ b/tests/framework/base/windowmanager/app30/src/android/server/wm/app30/Components.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.server.wm.app30;
+
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+
+import android.content.ComponentName;
+import android.server.wm.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+ public static final ComponentName SDK_30_TEST_ACTIVITY =
+ component(Components.class, TEST_ACTIVITY.getClassName());
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CompatScaleTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CompatScaleTests.java
new file mode 100644
index 0000000..dce47ab
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CompatScaleTests.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.UI_SCALING_TEST_ACTIVITY;
+
+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.content.ComponentName;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+
+/**
+ * The test is focused on compatibility scaling, and tests the feature form two sides.
+ * 1. It checks that the applications "sees" the metrics in PXs, but the DP metrics remain the same.
+ * 2. It checks the WindowManagerServer state, and makes sure that the scaling is correctly
+ * reflected in the WindowState.
+ *
+ * This is achieved by launching a {@link android.server.wm.app.UiScalingTestActivity} and having it
+ * reporting the metrics it receives.
+ * The Activity also draws 3 UI elements: a text, a red square with a 100dp side and a blue square
+ * with a 100px side.
+ * The text and the red square should have the same when rendered on the screen (by HWC) both when
+ * the compat downscaling is enabled and disabled.
+ * TODO(b/180098454): Add tests to make sure that the UI elements, which have their sizes declared
+ * in DPs (the text and the red square) have the same sizes on the screen (after composition).
+ *
+ * <p>Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:CompatScaleTests
+ */
+@RunWith(Parameterized.class)
+public class CompatScaleTests extends ActivityManagerTestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ { "DOWNSCALE_50", 0.5f },
+ { "DOWNSCALE_62_5", 0.625f },
+ { "DOWNSCALE_75", 0.75f },
+ { "DOWNSCALE_87_5", 0.875f },
+ });
+ }
+
+ private static final ComponentName ACTIVITY_UNDER_TEST = UI_SCALING_TEST_ACTIVITY;
+ private static final String PACKAGE_UNDER_TEST = ACTIVITY_UNDER_TEST.getPackageName();
+ private static final float EPSILON_GLOBAL_SCALE = 0.01f;
+
+ private final String mCompatChangeName;
+ private final float mCompatScale;
+ private final float mInvCompatScale;
+ private CommandSession.SizeInfo mAppSizesNormal;
+ private CommandSession.SizeInfo mAppSizesDownscaled;
+ private WindowManagerState.WindowState mWindowStateNormal;
+ private WindowManagerState.WindowState mWindowStateDownscaled;
+
+ public CompatScaleTests(String compatChangeName, float compatScale) {
+ mCompatChangeName = compatChangeName;
+ mCompatScale = compatScale;
+ mInvCompatScale = 1 / mCompatScale;
+ }
+
+ // TODO(b/180343437): replace @Before with @BeforeParam
+ @Before
+ public void launchInNormalAndDownscaleMode_collectSizesAndWindowState() {
+ // Launch activity with downscaling *disabled* and get the sizes it reports and its Window
+ // state.
+ launchActivity();
+ mAppSizesNormal = getActivityReportedSizes();
+ mWindowStateNormal = getActivityWindowState();
+
+ // Now launch the same activity with downscaling *enabled* and get the sizes it reports and
+ // its Window state.
+ enableDownscaling(mCompatChangeName);
+ launchActivity();
+ mAppSizesDownscaled = getActivityReportedSizes();
+ mWindowStateDownscaled = getActivityWindowState();
+ }
+
+ /**
+ * Tests that the Density DPI that the application receives from the
+ * {@link android.content.res.Configuration} is correctly scaled in the downscaled mode.
+ * @see android.content.res.Configuration#densityDpi
+ */
+ @Test
+ public void test_config_densityDpi_scalesCorrectly_inCompatDownscalingMode() {
+ assertScaled("Density DPI should scale by " + mCompatScale,
+ mAppSizesNormal.densityDpi, mCompatScale, mAppSizesDownscaled.densityDpi);
+ }
+
+ /**
+ * Tests that the screen sizes in DPs that the application receives from the
+ * {@link android.content.res.Configuration} are NOT scaled in the downscaled mode.
+ * @see android.content.res.Configuration#screenWidthDp
+ * @see android.content.res.Configuration#screenHeightDp
+ * @see android.content.res.Configuration#smallestScreenWidthDp
+ */
+ @Test
+ public void test_config_screenSize_inDPs_doesNotChange_inCompatDownscalingMode() {
+ assertEquals("Width shouldn't change",
+ mAppSizesNormal.widthDp, mAppSizesDownscaled.widthDp);
+ assertEquals("Height shouldn't change",
+ mAppSizesNormal.heightDp, mAppSizesDownscaled.heightDp);
+ assertEquals("Smallest Width shouldn't change",
+ mAppSizesNormal.smallestWidthDp, mAppSizesDownscaled.smallestWidthDp);
+ }
+
+ /**
+ * Tests that the Window sizes in PXs that the application receives from the
+ * {@link android.content.res.Configuration} are scaled correctly in the downscaled mode.
+ * @see android.content.res.Configuration#windowConfiguration
+ * @see android.app.WindowConfiguration#getBounds()
+ * @see android.app.WindowConfiguration#getAppBounds()
+ */
+ @Test
+ public void test_config_windowSizes_inPXs_scaleCorrectly_inCompatDownscalingMode() {
+ assertScaled("Width should scale by " + mCompatScale,
+ mAppSizesNormal.windowWidth, mCompatScale, mAppSizesDownscaled.windowWidth);
+ assertScaled("Height should scale by " + mCompatScale,
+ mAppSizesNormal.windowHeight, mCompatScale, mAppSizesDownscaled.windowHeight);
+ assertScaled("App width should scale by " + mCompatScale,
+ mAppSizesNormal.windowAppWidth, mCompatScale, mAppSizesDownscaled.windowAppWidth);
+ assertScaled("App height should scale by " + mCompatScale,
+ mAppSizesNormal.windowAppHeight, mCompatScale, mAppSizesDownscaled.windowAppHeight);
+ }
+
+ /**
+ * Tests that the {@link android.util.DisplayMetrics} in PXs that the application can obtain via
+ * {@link android.content.res.Resources#getDisplayMetrics()} are scaled correctly in the
+ * downscaled mode.
+ * @see android.util.DisplayMetrics#widthPixels
+ * @see android.util.DisplayMetrics#heightPixels
+ */
+ @Test
+ public void test_displayMetrics_inPXs_scaleCorrectly_inCompatDownscalingMode() {
+ assertScaled("Width should scale by " + mCompatScale,
+ mAppSizesNormal.metricsWidth, mCompatScale, mAppSizesDownscaled.metricsWidth);
+ assertScaled("Height should scale by " + mCompatScale,
+ mAppSizesNormal.metricsHeight, mCompatScale, mAppSizesDownscaled.metricsHeight);
+ }
+
+ /**
+ * Tests that the dimensions of a {@link android.view.Display} in PXs that the application can
+ * obtain via {@link android.view.View#getDisplay()} are scaled correctly in the downscaled
+ * mode.
+ * @see android.view.Display#getSize(android.graphics.Point)
+ */
+ @Test
+ public void test_displaySize_inPXs_scaleCorrectly_inCompatDownscalingMode() {
+ assertScaled("Width should scale by " + mCompatScale,
+ mAppSizesNormal.displayWidth, mCompatScale, mAppSizesDownscaled.displayWidth);
+ assertScaled("Height should scale by " + mCompatScale,
+ mAppSizesNormal.displayHeight, mCompatScale, mAppSizesDownscaled.displayHeight);
+ }
+
+ /**
+ * Test that compatibility downscaling is reflected correctly on the WM side.
+ * @see android.server.wm.WindowManagerState.WindowState
+ */
+ @Test
+ public void test_windowState_inCompatDownscalingMode() {
+ // Check the "normal" window's state for disabled compat mode and appropriate global scale.
+ assertFalse("The Window should not be in the size compat mode",
+ mWindowStateNormal.isInSizeCompatMode());
+ assertEquals("The window should not be scaled",
+ 1f, mWindowStateNormal.getGlobalScale(), EPSILON_GLOBAL_SCALE);
+
+ // Check the "downscaled" window's state for enabled compat mode and appropriate global
+ // scale.
+ assertTrue("The Window should be in the size compat mode",
+ mWindowStateDownscaled.isInSizeCompatMode());
+ assertEquals("The window should have global scale of " + mInvCompatScale,
+ mInvCompatScale, mWindowStateDownscaled.getGlobalScale(), EPSILON_GLOBAL_SCALE);
+
+ // Make sure the frame sizes changed correctly.
+ assertEquals("Window frame on should not change",
+ mWindowStateNormal.getFrame(), mWindowStateDownscaled.getFrame());
+ assertScaled("Requested width should scale by " + mCompatScale,
+ mWindowStateNormal.getRequestedWidth(), mCompatScale,
+ mWindowStateDownscaled.getRequestedWidth());
+ assertScaled("Requested height should scale by " + mCompatScale,
+ mWindowStateNormal.getRequestedHeight(), mCompatScale,
+ mWindowStateDownscaled.getRequestedHeight());
+ }
+
+ @After
+ public void tearDown() {
+ disableDownscaling(mCompatChangeName);
+ }
+
+ private void launchActivity() {
+ launchActivityInNewTask(ACTIVITY_UNDER_TEST);
+ mWmState.computeState(new WaitForValidActivityState(ACTIVITY_UNDER_TEST));
+ }
+
+ private CommandSession.SizeInfo getActivityReportedSizes() {
+ final CommandSession.SizeInfo details =
+ getLastReportedSizesForActivity(ACTIVITY_UNDER_TEST);
+ assertNotNull(details);
+ return details;
+ }
+
+ private WindowManagerState.WindowState getActivityWindowState() {
+ final int TYPE_BASE_APPLICATION = 1; // from WindowManager.java
+ final WindowManagerState.WindowState window =
+ mWmState.getWindowByPackageName(PACKAGE_UNDER_TEST, TYPE_BASE_APPLICATION);
+ assertNotNull(window);
+ return window;
+ }
+
+ private static void enableDownscaling(String compatChangeName) {
+ executeShellCommand("am compat enable " + compatChangeName + " " + PACKAGE_UNDER_TEST);
+ executeShellCommand("am compat enable DOWNSCALED " + PACKAGE_UNDER_TEST);
+ }
+
+ private static void disableDownscaling(String compatChangeName) {
+ executeShellCommand("am compat disable DOWNSCALED " + PACKAGE_UNDER_TEST);
+ executeShellCommand("am compat disable " + compatChangeName + " " + PACKAGE_UNDER_TEST);
+ }
+
+ private static void assertScaled(String message, int baseValue, float expectedScale,
+ int actualValue) {
+ // In order to account for possible rounding errors, let's calculate the actual scale and
+ // compare it's against the expected scale (allowing a small delta).
+ final float actualScale = ((float) actualValue) / baseValue;
+ assertEquals(message, expectedScale, actualScale, EPSILON_GLOBAL_SCALE);
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
new file mode 100644
index 0000000..ee709ca
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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_NOT_VISIBLE_ON_SCREEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+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.platform.test.annotations.Presubmit;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.displayhash.DisplayHash;
+import android.view.displayhash.DisplayHashManager;
+import android.view.displayhash.DisplayHashResultCallback;
+import android.view.displayhash.VerifiedDisplayHash;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+public class DisplayHashManagerTest {
+ private final Point mTestViewSize = new Point(200, 300);
+
+ private Instrumentation mInstrumentation;
+ private RelativeLayout mMainView;
+ private TestActivity mActivity;
+
+ private View mTestView;
+
+ private DisplayHashManager mDisplayHashManager;
+ private String mFirstHashAlgorithm;
+
+ private Executor mExecutor;
+
+ private SyncDisplayHashResultCallback mSyncDisplayHashResultCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ Context context = mInstrumentation.getContext();
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(context, TestActivity.class);
+ ActivityScenario<TestActivity> scenario = ActivityScenario.launch(intent);
+
+ scenario.onActivity(activity -> {
+ mActivity = activity;
+ mMainView = new RelativeLayout(activity);
+ activity.setContentView(mMainView);
+ });
+ mInstrumentation.waitForIdleSync();
+ mDisplayHashManager = context.getSystemService(DisplayHashManager.class);
+
+ Set<String> algorithms = mDisplayHashManager.getSupportedHashAlgorithms();
+ assertNotNull(algorithms);
+ assertNotEquals(0, algorithms.size());
+ mFirstHashAlgorithm = algorithms.iterator().next();
+ mExecutor = context.getMainExecutor();
+ mSyncDisplayHashResultCallback = new SyncDisplayHashResultCallback();
+ }
+
+ @Test
+ public void testGenerateAndVerifyDisplayHash() {
+ mInstrumentation.runOnMainSync(() -> {
+ final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+ mTestViewSize.y);
+ mTestView = new View(mActivity);
+ mTestView.setBackgroundColor(Color.BLUE);
+ mMainView.addView(mTestView, p);
+ mMainView.invalidate();
+ });
+ mInstrumentation.waitForIdleSync();
+
+ DisplayHash displayHash = generateDisplayHash(null);
+
+ VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
+ displayHash);
+ assertNotNull(verifiedDisplayHash);
+ assertEquals(mTestViewSize.x, verifiedDisplayHash.getBoundsInWindow().width());
+ assertEquals(mTestViewSize.y, verifiedDisplayHash.getBoundsInWindow().height());
+ }
+
+ @Test
+ public void testGenerateAndVerifyDisplayHash_BoundsInView() {
+ mInstrumentation.runOnMainSync(() -> {
+ final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+ mTestViewSize.y);
+ mTestView = new View(mActivity);
+ mTestView.setBackgroundColor(Color.BLUE);
+ mMainView.addView(mTestView, p);
+ mMainView.invalidate();
+ });
+ mInstrumentation.waitForIdleSync();
+
+ Rect bounds = new Rect(10, 20, mTestViewSize.x / 2, mTestViewSize.y / 2);
+ DisplayHash displayHash = generateDisplayHash(new Rect(bounds));
+
+ VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
+ displayHash);
+ assertNotNull(verifiedDisplayHash);
+ assertEquals(bounds.width(), verifiedDisplayHash.getBoundsInWindow().width());
+ assertEquals(bounds.height(), verifiedDisplayHash.getBoundsInWindow().height());
+ }
+
+ @Test
+ public void testGenerateAndVerifyDisplayHash_EmptyBounds() {
+ mInstrumentation.runOnMainSync(() -> {
+ final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+ mTestViewSize.y);
+ mTestView = new View(mActivity);
+ mTestView.setBackgroundColor(Color.BLUE);
+ mMainView.addView(mTestView, p);
+ mMainView.invalidate();
+ });
+ mInstrumentation.waitForIdleSync();
+ mTestView.generateDisplayHash(mFirstHashAlgorithm, new Rect(), mExecutor,
+ mSyncDisplayHashResultCallback);
+
+ int errorCode = mSyncDisplayHashResultCallback.getError();
+ assertEquals(DISPLAY_HASH_ERROR_INVALID_BOUNDS, errorCode);
+ }
+
+ @Test
+ public void testGenerateAndVerifyDisplayHash_BoundsBiggerThanView() {
+ mInstrumentation.runOnMainSync(() -> {
+ final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+ mTestViewSize.y);
+ mTestView = new View(mActivity);
+ mTestView.setBackgroundColor(Color.BLUE);
+ mMainView.addView(mTestView, p);
+ mMainView.invalidate();
+ });
+ mInstrumentation.waitForIdleSync();
+
+ Rect bounds = new Rect(0, 0, mTestViewSize.x + 100, mTestViewSize.y + 100);
+
+ DisplayHash displayHash = generateDisplayHash(new Rect(bounds));
+
+ VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
+ displayHash);
+ assertNotNull(verifiedDisplayHash);
+ assertEquals(mTestViewSize.x, verifiedDisplayHash.getBoundsInWindow().width());
+ assertEquals(mTestViewSize.y, verifiedDisplayHash.getBoundsInWindow().height());
+ }
+
+ @Test
+ public void testGenerateDisplayHash_BoundsOutOfView() {
+ mInstrumentation.runOnMainSync(() -> {
+ final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+ mTestViewSize.y);
+ mTestView = new View(mActivity);
+ mTestView.setBackgroundColor(Color.BLUE);
+ mMainView.addView(mTestView, p);
+ mMainView.invalidate();
+ });
+ mInstrumentation.waitForIdleSync();
+
+ Rect bounds = new Rect(mTestViewSize.x + 1, mTestViewSize.y + 1, mTestViewSize.x + 100,
+ mTestViewSize.y + 100);
+
+ mTestView.generateDisplayHash(mFirstHashAlgorithm, new Rect(bounds),
+ mExecutor, mSyncDisplayHashResultCallback);
+ int errorCode = mSyncDisplayHashResultCallback.getError();
+ assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
+ }
+
+ @Test
+ public void testGenerateDisplayHash_ViewOffscreen() {
+ mInstrumentation.runOnMainSync(() -> {
+ final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+ mTestViewSize.y);
+ mTestView = new View(mActivity);
+ mTestView.setBackgroundColor(Color.BLUE);
+ mTestView.setX(-mTestViewSize.x);
+ mMainView.addView(mTestView, p);
+ mMainView.invalidate();
+ });
+ mInstrumentation.waitForIdleSync();
+
+ mTestView.generateDisplayHash(mFirstHashAlgorithm, null, mExecutor,
+ mSyncDisplayHashResultCallback);
+
+ int errorCode = mSyncDisplayHashResultCallback.getError();
+ assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
+ }
+
+ @Test
+ public void testGenerateDisplayHash_WindowOffscreen() {
+ final WindowManager wm = mActivity.getWindowManager();
+ final WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mMainView = new RelativeLayout(mActivity);
+ windowParams.width = mTestViewSize.x;
+ windowParams.height = mTestViewSize.y;
+ windowParams.gravity = Gravity.LEFT | Gravity.TOP;
+ 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.addView(mTestView, p);
+ });
+ mInstrumentation.waitForIdleSync();
+
+ generateDisplayHash(null);
+
+ mInstrumentation.runOnMainSync(() -> {
+ windowParams.x = -mTestViewSize.x;
+ wm.updateViewLayout(mMainView, windowParams);
+ });
+ mInstrumentation.waitForIdleSync();
+
+ mSyncDisplayHashResultCallback.reset();
+ mTestView.generateDisplayHash(mFirstHashAlgorithm, null, mExecutor,
+ mSyncDisplayHashResultCallback);
+
+ int errorCode = mSyncDisplayHashResultCallback.getError();
+ assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
+ }
+
+ private DisplayHash generateDisplayHash(Rect bounds) {
+ mTestView.generateDisplayHash(mFirstHashAlgorithm, bounds, mExecutor,
+ mSyncDisplayHashResultCallback);
+
+ DisplayHash displayHash = mSyncDisplayHashResultCallback.getDisplayHash();
+ assertNotNull(displayHash);
+
+ return displayHash;
+ }
+
+ public static class TestActivity extends Activity {
+ private final ArrayList<View> mViews = new ArrayList<>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ void addWindow(View view, WindowManager.LayoutParams attrs) {
+ getWindowManager().addView(view, attrs);
+ mViews.add(view);
+ }
+
+ void removeAllWindows() {
+ for (View view : mViews) {
+ getWindowManager().removeViewImmediate(view);
+ }
+ mViews.clear();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ removeAllWindows();
+ }
+ }
+
+ private static class SyncDisplayHashResultCallback implements DisplayHashResultCallback {
+ private static final int SCREENSHOT_WAIT_TIME_S = 1;
+ private DisplayHash mDisplayHash;
+ private int mError;
+ private CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+ public void reset() {
+ mCountDownLatch = new CountDownLatch(1);
+ }
+
+ public DisplayHash getDisplayHash() {
+ try {
+ mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ }
+ return mDisplayHash;
+ }
+
+ public int getError() {
+ try {
+ mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ }
+ return mError;
+ }
+
+ @Override
+ public void onDisplayHashResult(@NonNull DisplayHash displayHash) {
+ mDisplayHash = displayHash;
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onDisplayHashError(int errorCode) {
+ mError = errorCode;
+ mCountDownLatch.countDown();
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTest.java b/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTest.java
index b352df2..8166ab9 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTest.java
@@ -16,6 +16,10 @@
package android.server.wm;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+
import android.platform.test.annotations.Presubmit;
import org.junit.Test;
@@ -24,7 +28,20 @@
public class ForceRelayoutTest extends ForceRelayoutTestBase {
@Test
- public void testNoRelayoutWhenInsetsChange() throws Throwable {
- testRelayoutWhenInsetsChange(false /* testRelayoutWhenInsetsChange */);
+ public void testNoRelayoutWhenInsetsChange_adjustPan() throws Throwable {
+ testRelayoutWhenInsetsChange(
+ false /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_PAN);
+ }
+
+ @Test
+ public void testNoRelayoutWhenInsetsChange_adjustNothing() throws Throwable {
+ testRelayoutWhenInsetsChange(
+ false /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_NOTHING);
+ }
+
+ @Test
+ public void testNoRelayoutWhenInsetsChange_adjustResize() throws Throwable {
+ testRelayoutWhenInsetsChange(
+ false /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_RESIZE);
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java
index 9ffe529..ac06678 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java
@@ -20,6 +20,7 @@
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -48,7 +49,7 @@
public ActivityTestRule<TestActivity> mDecorActivity = new ActivityTestRule<>(
TestActivity.class);
- void testRelayoutWhenInsetsChange(boolean expectRelayoutWhenInsetsChange)
+ void testRelayoutWhenInsetsChange(boolean expectRelayoutWhenInsetsChange, int softInputMode)
throws Throwable {
TestActivity activity = mDecorActivity.getActivity();
assertNotNull("test setup failed", activity.mLastContentInsets);
@@ -58,6 +59,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
activity.mLayoutHappened = false;
activity.mMeasureHappened = false;
+ activity.getWindow().setSoftInputMode(softInputMode);
activity.getWindow().getInsetsController().hide(systemBars());
});
activity.mZeroInsets.await(180, TimeUnit.SECONDS);
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 6644990..c26ccb1 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -258,12 +258,17 @@
mActivityRule.runOnUiThread(() -> {
mVr.relayout(bigEdgeLength, bigEdgeLength);
});
- WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
- mEmbeddedView, null);
- // We need to draw twice to make sure the first buffer actually
- // arrives.
- WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
- mEmbeddedView, null);
+ mInstrumentation.waitForIdleSync();
+
+ // We use frameCommitCallback because we need to ensure HWUI
+ // has actually queued the frame.
+ final CountDownLatch latch = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ mEmbeddedView.getViewTreeObserver().registerFrameCommitCallback(
+ latch::countDown);
+ mEmbeddedView.invalidate();
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
// But after the click should hit.
CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TvMaxWindowSizeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/TvMaxWindowSizeTests.java
new file mode 100644
index 0000000..4daae77
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TvMaxWindowSizeTests.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.server.wm;
+
+
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app30.Components.SDK_30_TEST_ACTIVITY;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import static java.util.Objects.requireNonNull;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * The goal of this test is to make sure that on Android TV applications with target SDK version
+ * lower than S do not get a larger than 1080p (1920x1080) Window.
+ *
+ * <p>Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:TvMaxWindowSizeTests
+ */
+public class TvMaxWindowSizeTests extends ActivityManagerTestBase {
+
+ private int mDisplayLongestWidth;
+ private int mDisplayShortestWidth;
+
+ @Before
+ public void setUp() {
+ // We only need to run this on TV.
+ final PackageManager pm = mInstrumentation.getContext().getPackageManager();
+ final boolean isTv = pm.hasSystemFeature(FEATURE_LEANBACK) ||
+ pm.hasSystemFeature(FEATURE_LEANBACK_ONLY);
+ assumeTrue(isTv);
+
+ // Get the real size of the display.
+ final DisplayManager dm = mInstrumentation.getContext()
+ .getSystemService(DisplayManager.class);
+ requireNonNull(dm);
+ final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+ assumeNotNull(display);
+ final Point displaySize = new Point();
+ display.getRealSize(displaySize);
+ mDisplayLongestWidth = Math.max(displaySize.x, displaySize.y);
+ mDisplayShortestWidth = Math.min(displaySize.x, displaySize.y);
+ }
+
+ @Test
+ public void test_preSApplication_1080p_windowSizeCap() {
+ // Only run this if the resolution is over 1080p (at least on one side).
+ assumeFalse(mDisplayLongestWidth <= 1920 && mDisplayShortestWidth <= 1080);
+
+ final CommandSession.SizeInfo sizeInfo = launchAndGetReportedSizes(SDK_30_TEST_ACTIVITY);
+
+ final int longestWidth = Math.max(sizeInfo.windowAppWidth, sizeInfo.windowAppHeight);
+ final int shortestWidth = Math.min(sizeInfo.windowAppWidth, sizeInfo.windowAppHeight);
+
+ assertThat(longestWidth, lessThanOrEqualTo(1920));
+ assertThat(shortestWidth, lessThanOrEqualTo(1080));
+ }
+
+ @Test
+ public void test_windowSize_notLargerThan_displaySize() {
+ final CommandSession.SizeInfo sizeInfo = launchAndGetReportedSizes(TEST_ACTIVITY);
+
+ final int longestWidth = Math.max(sizeInfo.windowAppWidth, sizeInfo.windowAppHeight);
+ final int shortestWidth = Math.min(sizeInfo.windowAppWidth, sizeInfo.windowAppHeight);
+
+ assertThat(longestWidth, lessThanOrEqualTo(mDisplayLongestWidth));
+ assertThat(shortestWidth, lessThanOrEqualTo(mDisplayShortestWidth));
+ }
+
+ private CommandSession.SizeInfo launchAndGetReportedSizes(ComponentName componentName) {
+ startActivityOnDisplay(Display.DEFAULT_DISPLAY, componentName);
+ mWmState.computeState(new WaitForValidActivityState(componentName));
+ final CommandSession.SizeInfo sizeInfo = getLastReportedSizesForActivity(componentName);
+ assertNotNull(sizeInfo);
+ return sizeInfo;
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java b/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java
index f3aebf8..99e9659 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java
@@ -97,7 +97,7 @@
@Test
public void testDevSettingPrecedence() {
try (SettingsSession<Integer> devDialogShow =
- globalIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
+ secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
SettingsSession<Integer> userDialogHide =
globalIntSession(Settings.Global.HIDE_ERROR_DIALOGS);
SettingsSession<Integer> showOnFirstCrash =
@@ -105,7 +105,6 @@
) {
devDialogShow.set(1);
showOnFirstCrash.set(1);
- // I recon this would require pushing a configuration change
userDialogHide.set(1);
launchActivityNoWait(Components.CRASHING_ACTIVITY);
@@ -165,9 +164,11 @@
// wait for app to be focused
mWmState.waitAndAssertAppFocus(Components.UNRESPONSIVE_ACTIVITY.getPackageName(),
2_000 /* waitTime */);
- // wait for input manager to get the new focus app
- SystemClock.sleep(500);
- injectKey(KeyEvent.KEYCODE_BACK, false /* longPress */, false /* sync */);
+ // queue up enough key events to trigger an ANR
+ for (int i = 0; i < 14; i++) {
+ injectKey(KeyEvent.KEYCODE_TAB, false /* longPress */, false /* sync */);
+ SystemClock.sleep(500);
+ }
ensureNoCrashDialog(Components.UNRESPONSIVE_ACTIVITY);
ensureHomeFocused();
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
index a299c8c..6485340 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -706,9 +706,9 @@
}
// When the lock screen is removed, the ShowWhenLocked activity will be dismissed using the
- // back button, which should send it to the stopped state.
- waitAndAssertActivityStates(state(showWhenLockedActivity, ON_STOP));
- LifecycleVerifier.assertResumeToStopSequence(
+ // back button, which should finish the activity.
+ waitAndAssertActivityStates(state(showWhenLockedActivity, ON_DESTROY));
+ LifecycleVerifier.assertResumeToDestroySequence(
ShowWhenLockedCallbackTrackingActivity.class, getLifecycleLog());
}
diff --git a/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/ForceRelayoutSdk29Test.java b/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/ForceRelayoutSdk29Test.java
index 1860571..e052651 100644
--- a/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/ForceRelayoutSdk29Test.java
+++ b/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/ForceRelayoutSdk29Test.java
@@ -16,6 +16,10 @@
package android.server.wm;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+
import android.platform.test.annotations.Presubmit;
import org.junit.Test;
@@ -24,7 +28,20 @@
public class ForceRelayoutSdk29Test extends ForceRelayoutTestBase {
@Test
- public void testRelayoutWhenInsetsChange() throws Throwable {
- testRelayoutWhenInsetsChange(true /* testRelayoutWhenInsetsChange */);
+ public void testNoRelayoutWhenInsetsChange_adjustPan() throws Throwable {
+ testRelayoutWhenInsetsChange(
+ false /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_PAN);
+ }
+
+ @Test
+ public void testNoRelayoutWhenInsetsChange_adjustNothing() throws Throwable {
+ testRelayoutWhenInsetsChange(
+ false /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_NOTHING);
+ }
+
+ @Test
+ public void testRelayoutWhenInsetsChange_adjustResize() throws Throwable {
+ testRelayoutWhenInsetsChange(
+ true /* expectRelayoutWhenInsetsChange */, SOFT_INPUT_ADJUST_RESIZE);
}
}
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 020e435..c403771 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
@@ -116,6 +116,7 @@
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.Instrumentation;
+import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -1203,6 +1204,10 @@
.getBoolean(android.R.bool.config_perDisplayFocusEnabled);
}
+ protected static void removeLockCredential() {
+ runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
+ }
+
protected static boolean remoteInsetsControllerControlsSystemBars() {
return getInstrumentation().getTargetContext().getResources()
.getBoolean(android.R.bool.config_remoteInsetsControllerControlsSystemBars);
@@ -1361,7 +1366,6 @@
public LockScreenSession(int flags) {
mIsLockDisabled = isLockDisabled();
- mLockCredentialSet = false;
// Enable lock screen (swipe) by default.
setLockDisabled(false);
if ((flags & FLAG_REMOVE_ACTIVITIES_ON_CLOSE) != 0) {
@@ -1388,11 +1392,6 @@
return this;
}
- private void removeLockCredential() {
- runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
- mLockCredentialSet = false;
- }
-
LockScreenSession disableLockScreen() {
setLockDisabled(true);
return this;
@@ -1454,6 +1453,7 @@
setLockDisabled(mIsLockDisabled);
if (mLockCredentialSet) {
removeLockCredential();
+ mLockCredentialSet = false;
}
// Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
@@ -2480,8 +2480,34 @@
* to collect multiple errors.
*/
private class PostAssertionRule extends ErrorCollector {
+ private Throwable mLastError;
+
@Override
protected void verify() throws Throwable {
+ if (mLastError != null) {
+ // Try to recover the bad state of device to avoid subsequent test failures.
+ final KeyguardManager kgm = mContext.getSystemService(KeyguardManager.class);
+ if (kgm != null && kgm.isKeyguardLocked()) {
+ mLastError.addSuppressed(new IllegalStateException("Keyguard is locked"));
+ // To clear the credential immediately, the screen need to be turned on.
+ pressWakeupButton();
+ removeLockCredential();
+ // Off/on to refresh the keyguard state.
+ pressSleepButton();
+ pressWakeupButton();
+ pressUnlockButton();
+ }
+ final String overlayDisplaySettings = Settings.Global.getString(
+ mContext.getContentResolver(), Settings.Global.OVERLAY_DISPLAY_DEVICES);
+ if (overlayDisplaySettings != null && overlayDisplaySettings.length() > 0) {
+ mLastError.addSuppressed(new IllegalStateException(
+ "Overlay display is found: " + overlayDisplaySettings));
+ // Remove the overlay display because it may obscure the screen and causes the
+ // next tests to fail.
+ SettingsSession.delete(Settings.Global.getUriFor(
+ Settings.Global.OVERLAY_DISPLAY_DEVICES));
+ }
+ }
if (!sIllegalTaskStateFound) {
// Skip if a illegal task state was already found in previous test, or all tests
// afterward could also fail and fire unnecessary false alarms.
@@ -2494,6 +2520,12 @@
}
super.verify();
}
+
+ @Override
+ public void addError(Throwable error) {
+ super.addError(error);
+ mLastError = error;
+ }
}
/**
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 418d298..5f210a4 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
@@ -1078,6 +1078,10 @@
public int smallestWidthDp;
public int densityDpi;
public int orientation;
+ public int windowWidth;
+ public int windowHeight;
+ public int windowAppWidth;
+ public int windowAppHeight;
SizeInfo() {
}
@@ -1097,6 +1101,10 @@
smallestWidthDp = config.smallestScreenWidthDp;
densityDpi = config.densityDpi;
orientation = config.orientation;
+ windowWidth = config.windowConfiguration.getBounds().width();
+ windowHeight = config.windowConfiguration.getBounds().height();
+ windowAppWidth = config.windowConfiguration.getAppBounds().width();
+ windowAppHeight = config.windowConfiguration.getAppBounds().height();
}
@Override
@@ -1105,6 +1113,8 @@
+ " displayWidth=" + displayWidth + " displayHeight=" + displayHeight
+ " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight
+ " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi
+ + " windowWidth=" + windowWidth + " windowHeight=" + windowHeight
+ + " windowAppWidth=" + windowAppWidth + " windowAppHeight=" + windowAppHeight
+ " orientation=" + orientation + "}";
}
@@ -1125,7 +1135,11 @@
&& metricsHeight == that.metricsHeight
&& smallestWidthDp == that.smallestWidthDp
&& densityDpi == that.densityDpi
- && orientation == that.orientation;
+ && orientation == that.orientation
+ && windowWidth == that.windowWidth
+ && windowHeight == that.windowHeight
+ && windowAppWidth == that.windowAppWidth
+ && windowAppHeight == that.windowAppHeight;
}
@Override
@@ -1140,6 +1154,10 @@
result = 31 * result + smallestWidthDp;
result = 31 * result + densityDpi;
result = 31 * result + orientation;
+ result = 31 * result + windowWidth;
+ result = 31 * result + windowHeight;
+ result = 31 * result + windowAppWidth;
+ result = 31 * result + windowAppHeight;
return result;
}
@@ -1159,6 +1177,10 @@
dest.writeInt(smallestWidthDp);
dest.writeInt(densityDpi);
dest.writeInt(orientation);
+ dest.writeInt(windowWidth);
+ dest.writeInt(windowHeight);
+ dest.writeInt(windowAppWidth);
+ dest.writeInt(windowAppHeight);
}
public void readFromParcel(Parcel in) {
@@ -1171,6 +1193,10 @@
smallestWidthDp = in.readInt();
densityDpi = in.readInt();
orientation = in.readInt();
+ windowWidth = in.readInt();
+ windowHeight = in.readInt();
+ windowAppWidth = in.readInt();
+ windowAppHeight = in.readInt();
}
public static final Creator<SizeInfo> CREATOR = new Creator<SizeInfo>() {
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 19c0faf..2a9f861 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
@@ -1694,9 +1694,14 @@
private Rect mContainingFrame;
private Rect mParentFrame;
private Rect mFrame;
+ private Rect mCompatFrame;
private Rect mSurfaceInsets = new Rect();
private Rect mGivenContentInsets = new Rect();
private Rect mCrop = new Rect();
+ private boolean mInSizeCompatMode;
+ private float mGlobalScale;
+ private int mRequestedWidth;
+ private int mRequestedHeight;
WindowState(WindowStateProto proto) {
super(proto.windowContainer);
@@ -1720,6 +1725,7 @@
mFrame = extract(windowFramesProto.frame);
mContainingFrame = extract(windowFramesProto.containingFrame);
mParentFrame = extract(windowFramesProto.parentFrame);
+ mCompatFrame = extract(windowFramesProto.compatFrame);
}
mSurfaceInsets = extract(proto.surfaceInsets);
if (mName.startsWith(STARTING_WINDOW_PREFIX)) {
@@ -1735,6 +1741,10 @@
mWindowType = 0;
}
collectDescendantsOfType(WindowState.class, this, mSubWindows);
+ mInSizeCompatMode = proto.inSizeCompatMode;
+ mGlobalScale = proto.globalScale;
+ mRequestedWidth = proto.requestedWidth;
+ mRequestedHeight = proto.requestedHeight;
}
boolean isStartingWindow() {
@@ -1777,6 +1787,10 @@
return mParentFrame;
}
+ public Rect getCompatFrame() {
+ return mCompatFrame;
+ }
+
Rect getCrop() {
return mCrop;
}
@@ -1789,6 +1803,22 @@
return mType;
}
+ public boolean isInSizeCompatMode() {
+ return mInSizeCompatMode;
+ }
+
+ public float getGlobalScale() {
+ return mGlobalScale;
+ }
+
+ public int getRequestedWidth() {
+ return mRequestedWidth;
+ }
+
+ public int getRequestedHeight() {
+ return mRequestedHeight;
+ }
+
private String getWindowTypeSuffix(int windowType) {
switch (windowType) {
case WINDOW_TYPE_STARTING:
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
index ee993ae..fb52381 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
@@ -153,7 +153,7 @@
return getter.get(getContentResolver(), uri.getLastPathSegment());
}
- protected static void delete(final Uri uri) {
+ public static void delete(final Uri uri) {
final List<String> segments = uri.getPathSegments();
if (segments.size() != 2) {
Log.w(TAG, "Unsupported uri for deletion: " + uri, new Throwable());
diff --git a/tests/inputmethod/mockime/res/xml/method.xml b/tests/inputmethod/mockime/res/xml/method.xml
index 5b3cf85..48f4a78 100644
--- a/tests/inputmethod/mockime/res/xml/method.xml
+++ b/tests/inputmethod/mockime/res/xml/method.xml
@@ -16,5 +16,6 @@
-->
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
- android:supportsInlineSuggestions="true">
+ android:supportsInlineSuggestions="true"
+ android:configChanges="fontScale">
</input-method>
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
index 49983b8..902db86 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
@@ -16,6 +16,8 @@
package android.view.inputmethod.cts;
+import static android.content.pm.ActivityInfo.CONFIG_DENSITY;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -77,6 +79,7 @@
private boolean mSubtypeOverridesImplicitlyEnabledSubtype;
private int mSubtypeId;
private InputMethodSubtype mInputMethodSubtype;
+ private int mHandledConfigChanges;
@Before
public void setup() {
@@ -85,7 +88,9 @@
mClassName = InputMethodSettingsActivityStub.class.getName();
mLabel = "test";
mSettingsActivity = "android.view.inputmethod.cts.InputMethodSettingsActivityStub";
- mInputMethodInfo = new InputMethodInfo(mPackageName, mClassName, mLabel, mSettingsActivity);
+ mHandledConfigChanges = CONFIG_DENSITY;
+ mInputMethodInfo = new InputMethodInfo(
+ mPackageName, mClassName, mLabel, mSettingsActivity, mHandledConfigChanges);
mSubtypeNameResId = 0;
mSubtypeIconResId = 0;
@@ -155,6 +160,7 @@
String expectedId = component.flattenToShortString();
assertEquals(expectedId, info.getId());
assertEquals(mClassName, info.getServiceName());
+ assertEquals(mHandledConfigChanges, info.getConfigChanges());
}
@Test
@@ -174,7 +180,7 @@
@Test
public void testEquals() {
InputMethodInfo inputMethodInfo = new InputMethodInfo(mPackageName, mClassName, mLabel,
- mSettingsActivity);
+ mSettingsActivity, mHandledConfigChanges);
assertTrue(inputMethodInfo.equals(mInputMethodInfo));
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index b2a0287..d8624aee 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -66,6 +66,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.SystemUtil;
import com.android.cts.mockime.ImeCommand;
import com.android.cts.mockime.ImeEvent;
import com.android.cts.mockime.ImeEventStream;
@@ -77,6 +78,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -93,6 +95,10 @@
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(20);
private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+ private static final String ERASE_FONT_SCALE_CMD = "settings delete system font_scale";
+ // 1.2 is an arbitrary value.
+ private static final String PUT_FONT_SCALE_CMD = "settings put system font_scale 1.2";
+
@Rule
public final UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
@@ -247,6 +253,39 @@
}
}
+ @Test
+ public void testHandlesConfigChanges() throws Exception {
+ try (MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+ // MockIme handles fontScale. Make sure changing fontScale doesn't restart IME.
+ toggleFontScale();
+ expectImeVisible(TIMEOUT);
+ // Make sure IME was not restarted.
+ notExpectEvent(stream, event -> "onCreate".equals(event.getEventName()), TIMEOUT);
+ }
+ }
+
+ // Font scale is a global configuration.
+ // This function will delete any previous font scale changes, apply one, and remove it.
+ private void toggleFontScale() {
+ try {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ SystemUtil.runShellCommand(instrumentation, ERASE_FONT_SCALE_CMD);
+ instrumentation.waitForIdleSync();
+ SystemUtil.runShellCommand(instrumentation, PUT_FONT_SCALE_CMD);
+ instrumentation.waitForIdleSync();
+ SystemUtil.runShellCommand(instrumentation, ERASE_FONT_SCALE_CMD);
+ } catch (IOException io) {
+ fail("Couldn't apply font scale.");
+ }
+ }
+
private static void assertSynthesizedSoftwareKeyEvent(KeyEvent keyEvent, int expectedAction,
int expectedKeyCode, long expectedEventTimeBefore, long expectedEventTimeAfter) {
if (keyEvent.getEventTime() < expectedEventTimeBefore
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 7c175c5..428ac60 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -117,6 +117,7 @@
private static final String ACTION_TRIGGER = "broadcast_action_trigger";
private static final String EXTRA_DISMISS_DIALOG = "extra_dismiss_dialog";
+ private static final String EXTRA_SHOW_SOFT_INPUT = "extra_show_soft_input";
private static final int NEW_KEYBOARD_HEIGHT = 400;
@Rule
@@ -559,7 +560,6 @@
runImeVisibilityWhenImeTransitionBetweenActivities(false /* instant */);
}
- @Ignore("b/179491219")
@AppModeInstant
@Test
public void testImeVisibilityWhenImeTransitionBetweenActivities_Instant() throws Exception {
@@ -572,7 +572,6 @@
runImeVisibilityTestWhenForceStopPackage(false /* instant */);
}
- @Ignore("b/179491012")
@AppModeInstant
@Test
public void testImeInvisibleWhenForceStopPkgProcess_Instant() throws Exception {
@@ -659,6 +658,11 @@
try (AutoCloseable closable = launchRemoteActivitySync(TEST_ACTIVITY, instant, TIMEOUT,
Map.of(EXTRA_KEY_PRIVATE_IME_OPTIONS, marker))) {
expectEvent(stream, editorMatcher("onStartInput", marker), START_INPUT_TIMEOUT);
+ expectImeInvisible(TIMEOUT);
+
+ // Request showSoftInput, expect the request is valid and soft-keyboard visible.
+ triggerActionWithBroadcast(ACTION_TRIGGER, TEST_ACTIVITY.getPackageName(),
+ EXTRA_SHOW_SOFT_INPUT);
expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
@@ -678,6 +682,10 @@
long timeout, Map<String, String> extras) {
final StringBuilder commandBuilder = new StringBuilder();
if (instant) {
+ // Override app-links domain verification.
+ runShellCommand(
+ String.format("pm set-app-links-user-selection --user cur --package %s true %s",
+ componentName.getPackageName(), TEST_ACTIVITY_URI.getHost()));
final Uri uri = formatStringIntentParam(TEST_ACTIVITY_URI, extras);
commandBuilder.append(String.format("am start -a %s -c %s %s",
Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE, uri.toString()));
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java
index 639be3b..4f2dbeb 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java
@@ -124,6 +124,10 @@
long timeout) {
final String command;
if (instant) {
+ // Override app-links domain verification.
+ runShellCommand(
+ String.format("pm set-app-links-user-selection --user cur --package %s true %s",
+ TEST_ACTIVITY.getPackageName(), TEST_ACTIVITY_URI.getHost()));
final Uri uri = formatStringIntentParam(
TEST_ACTIVITY_URI, EXTRA_KEY_PRIVATE_IME_OPTIONS, privateImeOptions);
command = String.format("am start -a %s -c %s %s",
@@ -147,7 +151,6 @@
testTargetPackageIsVisibleFromIme(false /* instant */);
}
- @Ignore("b/179983398")
@AppModeInstant
@Test
public void testTargetPackageIsVisibleFromImeInstant() throws Exception {
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 2b35554..207c440 100644
--- a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
+++ b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
@@ -30,13 +30,13 @@
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
-import com.android.compatibility.common.util.ImeAwareEditText;
-
/**
* A test {@link Activity} that automatically shows the input method.
*/
@@ -48,9 +48,11 @@
"android.view.inputmethod.ctstestapp.EXTRA_KEY_SHOW_DIALOG";
private static final String EXTRA_DISMISS_DIALOG = "extra_dismiss_dialog";
+ private static final String EXTRA_SHOW_SOFT_INPUT = "extra_show_soft_input";
private static final String ACTION_TRIGGER = "broadcast_action_trigger";
private AlertDialog mDialog;
+ private EditText mEditor;
private final Handler mHandler = new Handler(Looper.myLooper());
private BroadcastReceiver mBroadcastReceiver;
@@ -102,15 +104,14 @@
mDialog.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_PAN);
mDialog.show();
} else {
- final ImeAwareEditText editText = new ImeAwareEditText(this);
- editText.setHint("editText");
+ mEditor = new EditText(this);
+ mEditor.setHint("editText");
final String privateImeOptions = getStringIntentExtra(EXTRA_KEY_PRIVATE_IME_OPTIONS);
if (privateImeOptions != null) {
- editText.setPrivateImeOptions(privateImeOptions);
+ mEditor.setPrivateImeOptions(privateImeOptions);
}
- editText.requestFocus();
- editText.scheduleShowSoftInput();
- layout.addView(editText);
+ mEditor.requestFocus();
+ layout.addView(mEditor);
}
setContentView(layout);
@@ -122,7 +123,16 @@
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getBooleanExtra(EXTRA_DISMISS_DIALOG, false)) {
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+
+ if (extras.containsKey(EXTRA_SHOW_SOFT_INPUT)) {
+ getSystemService(InputMethodManager.class).showSoftInput(mEditor, 0);
+ }
+
+ if (extras.getBoolean(EXTRA_DISMISS_DIALOG, false)) {
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
diff --git a/tests/libcore/luni/AndroidTest.xml b/tests/libcore/luni/AndroidTest.xml
index e0fc82a33..bb675f0 100644
--- a/tests/libcore/luni/AndroidTest.xml
+++ b/tests/libcore/luni/AndroidTest.xml
@@ -22,6 +22,9 @@
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- libcore.java.net.SocketTest requires wifi -->
+ <option name="run-command" value="settings put global wifi_on 1" />
+ <option name="run-command" value="svc wifi enable" />
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/user.home" />
<option name="teardown-command" value="rm -rf /data/local/tmp/ctslibcore" />
diff --git a/tests/libcore/okhttp/AndroidTest.xml b/tests/libcore/okhttp/AndroidTest.xml
index 771293e2..9ca68e0 100644
--- a/tests/libcore/okhttp/AndroidTest.xml
+++ b/tests/libcore/okhttp/AndroidTest.xml
@@ -22,6 +22,9 @@
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- This CTS test module requires wifi, ensure wifi is on -->
+ <option name="run-command" value="settings put global wifi_on 1" />
+ <option name="run-command" value="svc wifi enable" />
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/user.home" />
<option name="teardown-command" value="rm -rf /data/local/tmp/ctslibcore" />
diff --git a/tests/location/common/src/android/location/cts/common/ProviderRequestListenerCapture.java b/tests/location/common/src/android/location/cts/common/ProviderRequestChangedListenerCapture.java
similarity index 81%
rename from tests/location/common/src/android/location/cts/common/ProviderRequestListenerCapture.java
rename to tests/location/common/src/android/location/cts/common/ProviderRequestChangedListenerCapture.java
index 9522474..65797a2 100644
--- a/tests/location/common/src/android/location/cts/common/ProviderRequestListenerCapture.java
+++ b/tests/location/common/src/android/location/cts/common/ProviderRequestChangedListenerCapture.java
@@ -24,12 +24,16 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
-/** A {@link ProviderRequest.Listener} that automatically unregisters itself when closed. */
-public class ProviderRequestListenerCapture implements ProviderRequest.Listener, AutoCloseable {
+/**
+ * A {@link ProviderRequest.ChangedListener} that automatically unregisters itself
+ * when closed.
+ */
+public class ProviderRequestChangedListenerCapture implements
+ ProviderRequest.ChangedListener, AutoCloseable {
private final LocationManager mLocationManager;
private final LinkedBlockingQueue<Pair<String, ProviderRequest>> mProviderRequestChanges;
- public ProviderRequestListenerCapture(Context context) {
+ public ProviderRequestChangedListenerCapture(Context context) {
mLocationManager = context.getSystemService(LocationManager.class);
mProviderRequestChanges = new LinkedBlockingQueue<>();
}
@@ -46,6 +50,6 @@
@Override
public void close() throws Exception {
- mLocationManager.unregisterProviderRequestListener(this);
+ mLocationManager.removeProviderRequestChangedListener(this);
}
}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
index c878e33..8e19407 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
@@ -62,7 +62,7 @@
import android.location.cts.common.GetCurrentLocationCapture;
import android.location.cts.common.LocationListenerCapture;
import android.location.cts.common.LocationPendingIntentCapture;
-import android.location.cts.common.ProviderRequestListenerCapture;
+import android.location.cts.common.ProviderRequestChangedListenerCapture;
import android.location.cts.common.gnss.GnssAntennaInfoCapture;
import android.location.cts.common.gnss.GnssMeasurementsCapture;
import android.location.cts.common.gnss.GnssNavigationMessageCapture;
@@ -809,14 +809,14 @@
}
@Test
- public void testRegisterProviderRequestListener() throws Exception {
+ public void testAddProviderRequestListener() throws Exception {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
- try (ProviderRequestListenerCapture requestlistener = new ProviderRequestListenerCapture(
- mContext);
+ try (ProviderRequestChangedListenerCapture requestlistener =
+ new ProviderRequestChangedListenerCapture(mContext);
LocationListenerCapture locationListener = new LocationListenerCapture(mContext)) {
- mManager.registerProviderRequestListener(Executors.newSingleThreadExecutor(),
+ mManager.addProviderRequestChangedListener(Executors.newSingleThreadExecutor(),
requestlistener);
mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
Executors.newSingleThreadExecutor(), locationListener);
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
index d100fed..f8ebaac 100644
--- a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
@@ -21,10 +21,10 @@
import android.content.ComponentName;
import android.media.AudioFormat;
import android.media.MediaMetadata;
-import android.media.musicrecognition.MusicRecognitionManager;
import android.media.musicrecognition.MusicRecognitionService;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.util.Log;
import androidx.annotation.NonNull;
@@ -36,8 +36,10 @@
/** No-op implementation of MusicRecognitionService for testing purposes. */
public class CtsMusicRecognitionService extends MusicRecognitionService {
+ private static final String TAG = CtsMusicRecognitionService.class.getSimpleName();
+ public static final String SERVICE_PACKAGE = "android.musicrecognition.cts";
public static final ComponentName SERVICE_COMPONENT = new ComponentName(
- "android.musicrecognition.cts", CtsMusicRecognitionService.class.getName());
+ SERVICE_PACKAGE, CtsMusicRecognitionService.class.getName());
private static Watcher sWatcher;
@@ -54,7 +56,9 @@
if (sWatcher.failureCode != 0) {
callback.onRecognitionFailed(sWatcher.failureCode);
} else {
+ Log.i(TAG, "Reading audio stream...");
sWatcher.stream = readStream(stream);
+ Log.i(TAG, "Reading audio done.");
callback.onRecognitionSucceeded(sWatcher.result, sWatcher.resultExtras);
}
}
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
index 54e5f69..9a9ed7b 100644
--- a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
@@ -20,16 +20,23 @@
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import android.app.AppOpsManager;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioFormat;
@@ -39,16 +46,15 @@
import android.media.musicrecognition.MusicRecognitionManager;
import android.media.musicrecognition.RecognitionRequest;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.RequiredServiceRule;
-import com.android.compatibility.common.util.ShellUtils;
import com.google.common.util.concurrent.MoreExecutors;
@@ -70,6 +76,7 @@
public class MusicRecognitionManagerTest {
private static final String TAG = MusicRecognitionManagerTest.class.getSimpleName();
private static final long VERIFY_TIMEOUT_MS = 40_000;
+ private static final long VERIFY_APPOP_CHANGE_TIMEOUT_MS = 2000;
@Rule public TestName mTestName = new TestName();
@Rule
@@ -170,6 +177,62 @@
verify(mCallback, never()).onRecognitionSucceeded(any(), any(), any());
}
+ @Test
+ public void testRecordAudioOpsAreTracked() {
+ mWatcher.result = new MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_ARTIST, "artist")
+ .putString(MediaMetadata.METADATA_KEY_TITLE, "title")
+ .build();
+
+ final String packageName = CtsMusicRecognitionService.SERVICE_PACKAGE;
+ final int uid = Process.myUid();
+
+ final AppOpsManager appOpsManager = getInstrumentation().getContext()
+ .getSystemService(AppOpsManager.class);
+ final AppOpsManager.OnOpActiveChangedListener listener = mock(
+ AppOpsManager.OnOpActiveChangedListener.class);
+ // Assert the app op is not started
+ assertFalse(appOpsManager.isOpActive(AppOpsManager.OPSTR_RECORD_AUDIO, uid, packageName));
+
+ // Start watching for record audio op
+ appOpsManager.startWatchingActive(new String[] { AppOpsManager.OPSTR_RECORD_AUDIO },
+ getInstrumentation().getContext().getMainExecutor(), listener);
+
+ // Invoke API
+ RecognitionRequest request = invokeMusicRecognitionApi();
+
+ // The app op should start
+ verify(listener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS)
+ .only()).onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
+ eq(uid), eq(packageName), eq(true));
+
+ // Wait for streaming to finish.
+ reset(listener);
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ // The app op should finish
+ verify(listener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS)
+ .only()).onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
+ eq(uid), eq(packageName), eq(false));
+
+
+ // Start with a clean slate
+ reset(listener);
+
+ // Stop watching for app op
+ appOpsManager.stopWatchingActive(listener);
+
+ // No other callbacks expected
+ verify(listener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS).times(0))
+ .onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
+ anyInt(), anyString(), anyBoolean());
+ }
+
+
private RecognitionRequest invokeMusicRecognitionApi() {
AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC, 16_000,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 256_000);
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java b/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
index a2c82f4..b97a2cc 100644
--- a/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
+++ b/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
@@ -32,6 +32,7 @@
import org.junit.After;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -72,6 +73,9 @@
Context context = InstrumentationRegistry.getInstrumentation().getContext();
mDirectReportTestHelper = new SensorRatePermissionDirectReportTestHelper(context,
sensorType);
+ Assume.assumeTrue("Failed to create mDirectReportTestHelper!",
+ mDirectReportTestHelper != null);
+
mSensorManager = context.getSystemService(SensorManager.class);
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
mUserID = UserHandle.myUserId();
@@ -79,7 +83,9 @@
@After
public void tearDown() throws InterruptedException {
- mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+ if (mDirectReportTestHelper != null) {
+ mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+ }
}
@Test
@@ -126,9 +132,6 @@
// the sensor corresponds to a sampling rate of > 200 Hz and that the sensor supports
// direct channel.
Sensor s = mDirectReportTestHelper.getSensor();
- if (s == null) {
- return;
- }
if (s.getHighestDirectReportRateLevel() <= SensorDirectChannel.RATE_FAST
|| !s.isDirectChannelTypeSupported(SensorDirectChannel.TYPE_HARDWARE_BUFFER)) {
return;
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java b/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java
index 50984bd..ff6d658 100644
--- a/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java
+++ b/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java
@@ -28,6 +28,7 @@
import org.junit.After;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,13 +67,18 @@
Context context = InstrumentationRegistry.getInstrumentation().getContext();
mDirectReportTestHelper = new SensorRatePermissionDirectReportTestHelper(context,
sensorType);
+ Assume.assumeTrue("Failed to create mDirectReportTestHelper!",
+ mDirectReportTestHelper != null);
+
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
mUserID = UserHandle.myUserId();
}
@After
public void tearDown() throws InterruptedException {
- mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+ if (mDirectReportTestHelper != null) {
+ mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+ }
}
@Test
diff --git a/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java b/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java
index c869bf1..18e308e 100644
--- a/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java
+++ b/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java
@@ -20,7 +20,6 @@
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.SensorPrivacyManager;
-import android.hardware.cts.helpers.SensorRatePermissionDirectReportTestHelper;
import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
import android.hardware.cts.helpers.TestSensorEnvironment;
import android.hardware.cts.helpers.TestSensorEvent;
@@ -86,8 +85,13 @@
sensor,
SensorManager.SENSOR_DELAY_FASTEST,
(int) TimeUnit.SECONDS.toMicros(5));
+ Assume.assumeTrue("Failed to create mTestEnvironment!", mTestEnvironment != null);
+
mEventConnectionTestHelper = new SensorRatePermissionEventConnectionTestHelper(
mTestEnvironment);
+ Assume.assumeTrue("Failed to create mEventConnectionTestHelper!",
+ mEventConnectionTestHelper != null);
+
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
// In context of this app (targetSDK = 30), this returns the original supported min delay
// of the sensor
@@ -99,7 +103,9 @@
@After
public void tearDown() throws InterruptedException {
- mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+ if (mEventConnectionTestHelper != null) {
+ mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+ }
}
@Test
@@ -167,7 +173,7 @@
Long.MIN_VALUE, Long.MAX_VALUE);
Assert.assertTrue(mEventConnectionTestHelper.errorWhenExceedCappedRate(),
rateWhenMicToggleOn
- <= SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+ <= SensorRatePermissionEventConnectionTestHelper.CAPPED_SAMPLE_RATE_HZ);
// Flip the mic toggle off, clear all the events so far.
mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
@@ -181,7 +187,7 @@
events, Long.MIN_VALUE, Long.MAX_VALUE);
Assert.assertTrue(mEventConnectionTestHelper.errorWhenBelowExpectedRate(),
rateWhenMicToggleOff
- > SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+ > SensorRatePermissionEventConnectionTestHelper.CAPPED_SAMPLE_RATE_HZ);
listener.clearEvents();
testSensorManager.unregisterListener();
diff --git a/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java b/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java
index b912cc0..ff909e5 100644
--- a/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java
+++ b/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java
@@ -79,15 +79,22 @@
sensor,
SensorManager.SENSOR_DELAY_FASTEST,
(int) TimeUnit.SECONDS.toMicros(5));
+ Assume.assumeTrue("Failed to create mTestEnvironment!", mTestEnvironment != null);
+
mEventConnectionTestHelper = new SensorRatePermissionEventConnectionTestHelper(
mTestEnvironment);
+ Assume.assumeTrue("Failed to create mEventConnectionTestHelper!",
+ mEventConnectionTestHelper != null);
+
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
mUserID = UserHandle.myUserId();
}
@After
public void tearDown() throws InterruptedException {
- mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+ if (mEventConnectionTestHelper != null) {
+ mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+ }
}
@Test
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java b/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
index c52e4d6..653c22f 100644
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
+++ b/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
@@ -39,7 +39,6 @@
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Assert;
-import org.junit.Assume;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -72,8 +71,9 @@
mContext = InstrumentationRegistry.getInstrumentation().getContext();
SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- Assume.assumeTrue("Failed to find a sensor!", sensor != null);
-
+ if (sensor == null) {
+ return;
+ }
mTestEnvironment = new TestSensorEnvironment(
mContext,
sensor,
@@ -84,6 +84,9 @@
}
public void testResamplingEventConnections() throws Exception {
+ if (mTestEnvironment == null || mEventConnectionTestHelper == null) {
+ return;
+ }
// Start an app that registers a listener with high sampling rate
Intent intent = new Intent();
intent.setComponent(new ComponentName(
@@ -104,6 +107,9 @@
}
public void testSensorDefaultVerifications() throws Exception {
+ if (mTestEnvironment == null) {
+ return;
+ }
TestSensorOperation op = TestSensorOperation.createOperation(
mTestEnvironment,
NUM_EVENTS_COUNT);
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
index e409208..08654b4 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
@@ -35,7 +35,7 @@
* A helper class to test sensor APIs related to sampling rates of SensorEventConnections.
*/
public class SensorRatePermissionEventConnectionTestHelper {
- public static final int CAPPED_SAMPLE_RATE_HZ = 200;
+ public static final int CAPPED_SAMPLE_RATE_HZ = 220; // Capped rate 200 Hz + 10% headroom
// Set of sensors that are throttled
public static final ImmutableSet<Integer> CAPPED_SENSOR_TYPE_SET = ImmutableSet.of(
Sensor.TYPE_ACCELEROMETER,
diff --git a/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml b/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
index ec621da..d9dbe8a 100644
--- a/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
+++ b/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
@@ -13,11 +13,22 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<TextView
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/item"
- android:layout_width="120dp"
- android:layout_height="120dp"
- android:gravity="center"
- android:textStyle="bold"
- android:textSize="44sp" />
+ android:id="@+id/root"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Switch
+ android:id="@+id/toggle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/item"
+ android:layout_width="120dp"
+ android:layout_height="120dp"
+ android:gravity="center"
+ android:textStyle="bold"
+ android:textSize="44sp" />
+</LinearLayout>
+
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
index 2bc4e47..925fa69 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -40,7 +41,9 @@
import android.os.Bundle;
import android.platform.test.annotations.AppModeFull;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.AbsListView;
+import android.widget.CompoundButton;
import android.widget.ListView;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
@@ -64,6 +67,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
/**
* Test AppWidgets which host collection widgets.
@@ -164,7 +168,9 @@
extras.putString(BlockingBroadcastReceiver.KEY_PARAM, COUNTRY_LIST[position]);
Intent fillInIntent = new Intent();
fillInIntent.putExtras(extras);
- remoteViews.setOnClickFillInIntent(R.id.item, fillInIntent);
+ remoteViews.setOnClickFillInIntent(R.id.root, fillInIntent);
+ remoteViews.setOnCheckedChangeResponse(
+ R.id.toggle, RemoteViews.RemoteResponse.fromFillInIntent(fillInIntent));
if (position == 0) {
factoryCountDownLatch.countDown();
@@ -339,6 +345,82 @@
verifyItemClickIntents(3);
}
+ /**
+ * Verifies that setting the item at {@code index} to {@code newChecked} sends a broadcast with
+ * the proper country and checked extras filled in.
+ *
+ * @param index the index to test
+ * @param newChecked the new checked state, which must be different from the current value
+ */
+ private void verifyItemCheckedChangeIntents(int index, boolean newChecked) throws Throwable {
+ BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver();
+ mActivityRule.runOnUiThread(() -> receiver.register(BROADCAST_ACTION));
+
+ mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
+ PollingCheck.waitFor(() -> mStackView.getCurrentView() != null);
+
+ mActivityRule.runOnUiThread(
+ () -> {
+ ViewGroup currentView = (ViewGroup) mStackView.getCurrentView();
+ CompoundButton toggle =
+ findFirstMatchingView(currentView, v -> v instanceof CompoundButton);
+ if (newChecked) {
+ assertFalse(toggle.isChecked());
+ } else {
+ assertTrue(toggle.isChecked());
+ }
+ toggle.setChecked(newChecked);
+ });
+ assertEquals(COUNTRY_LIST[index],
+ receiver.getParam(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ if (newChecked) {
+ assertTrue(receiver.result.getBooleanExtra(RemoteViews.EXTRA_CHECKED, false));
+ } else {
+ assertFalse(receiver.result.getBooleanExtra(RemoteViews.EXTRA_CHECKED, true));
+ }
+ }
+
+ @Test
+ public void testSetOnCheckedChangePendingIntent() throws Throwable {
+ if (!mHasAppWidgets) {
+ return;
+ }
+
+ // Toggle the view twice to get the true and false broadcasts.
+ verifyItemCheckedChangeIntents(0, true);
+ verifyItemCheckedChangeIntents(0, false);
+ verifyItemCheckedChangeIntents(0, true);
+
+ // Switch to another child
+ verifySetDisplayedChild(2);
+ verifyItemCheckedChangeIntents(2, true);
+
+ // And one more
+ verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_NEXT, 3);
+ verifyItemCheckedChangeIntents(3, true);
+ }
+
+ // Casting type for convenience. Test will fail either way if it's wrong.
+ @SuppressWarnings("unchecked")
+ private static <T extends View> T findFirstMatchingView(View view, Predicate<View> predicate) {
+ if (predicate.test(view)) {
+ return (T) view;
+ }
+ if ((!(view instanceof ViewGroup))) {
+ return null;
+ }
+
+ ViewGroup viewGroup = (ViewGroup) view;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ View result = findFirstMatchingView(viewGroup.getChildAt(i), predicate);
+ if (result != null) {
+ return (T) result;
+ }
+ }
+
+ return null;
+ }
+
private class ListScrollListener implements AbsListView.OnScrollListener {
private CountDownLatch mLatchToNotify;
diff --git a/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java b/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
index 4e45759..7f24d48 100644
--- a/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
+++ b/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
@@ -85,14 +85,15 @@
@Test
public void testRecordsIoPerformanceData() throws Exception {
String packageName = getContext().getPackageName();
- runShellCommand("dumpsys " + CAR_WATCHDOG_SERVICE_NAME
- + " --start_io --interval 5 --max_duration 120 --filter_packages " + packageName);
+ runShellCommand(
+ "dumpsys %s --start_perf --interval 5 --max_duration 120 --filter_packages %s",
+ CAR_WATCHDOG_SERVICE_NAME, packageName);
long writtenBytes = writeToDisk(testDir);
assertWithMessage("Failed to write data to dir '" + testDir.getAbsolutePath() + "'").that(
writtenBytes).isGreaterThan(0L);
// Sleep twice the collection interval to capture the entire write.
Thread.sleep(CAPTURE_WAIT_MS);
- String contents = runShellCommand("dumpsys " + CAR_WATCHDOG_SERVICE_NAME + " --stop_io");
+ String contents = runShellCommand("dumpsys %s --stop_perf", CAR_WATCHDOG_SERVICE_NAME);
Log.i(TAG, "stop results:" + contents);
assertWithMessage("Failed to custom collect I/O performance data").that(
contents).isNotEmpty();
diff --git a/tests/tests/car/src/android/car/cts/VehicleGearTest.java b/tests/tests/car/src/android/car/cts/VehicleGearTest.java
new file mode 100644
index 0000000..5601757
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/VehicleGearTest.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.car.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.VehicleGear;
+import android.car.VehiclePropertyIds;
+import android.util.Log;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+public class VehicleGearTest {
+ private static final String TAG = "VehicleGearTest";
+
+ /**
+ * Test for {@link VehicleGear#toString()}
+ */
+ @Test
+ public void testToString() {
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_UNKNOWN)).isEqualTo("GEAR_UNKNOWN");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_NEUTRAL)).isEqualTo("GEAR_NEUTRAL");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_REVERSE)).isEqualTo("GEAR_REVERSE");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_PARK)).isEqualTo("GEAR_PARK");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_DRIVE)).isEqualTo("GEAR_DRIVE");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_FIRST)).isEqualTo("GEAR_FIRST");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_SECOND)).isEqualTo("GEAR_SECOND");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_THIRD)).isEqualTo("GEAR_THIRD");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_FOURTH)).isEqualTo("GEAR_FOURTH");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_FIFTH)).isEqualTo("GEAR_FIFTH");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_SIXTH)).isEqualTo("GEAR_SIXTH");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_SEVENTH)).isEqualTo("GEAR_SEVENTH");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_EIGHTH)).isEqualTo("GEAR_EIGHTH");
+ assertThat(VehicleGear.toString(VehicleGear.GEAR_NINTH)).isEqualTo("GEAR_NINTH");
+ assertThat(VehicleGear.toString(3)).isEqualTo("0x3");
+ assertThat(VehicleGear.toString(12)).isEqualTo("0xc");
+
+ }
+
+ /**
+ * Test if all vehicle gears have a mapped string value.
+ */
+ @Test
+ public void testAllGearsAreMappedInToString() {
+ List<Integer> gears = 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/VehiclePropertyIdsTest.java b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
new file mode 100644
index 0000000..d5520ab
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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.car.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.VehicleGear;
+import android.car.VehiclePropertyIds;
+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";
+
+ /**
+ * Test for {@link VehiclePropertyIds#toString()}
+ */
+ @Test
+ public void testToString() {
+
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INVALID))
+ .isEqualTo("INVALID");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.IGNITION_STATE))
+ .isEqualTo("IGNITION_STATE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_DRIVER_SEAT))
+ .isEqualTo("INFO_DRIVER_SEAT");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_EV_BATTERY_CAPACITY))
+ .isEqualTo("INFO_EV_BATTERY_CAPACITY");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_EV_CONNECTOR_TYPE))
+ .isEqualTo("INFO_EV_CONNECTOR_TYPE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_EV_PORT_LOCATION))
+ .isEqualTo("INFO_EV_PORT_LOCATION");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_EXTERIOR_DIMENSIONS))
+ .isEqualTo("INFO_EXTERIOR_DIMENSIONS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_FUEL_CAPACITY))
+ .isEqualTo("INFO_FUEL_CAPACITY");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_FUEL_DOOR_LOCATION))
+ .isEqualTo("INFO_FUEL_DOOR_LOCATION");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_FUEL_TYPE))
+ .isEqualTo("INFO_FUEL_TYPE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_MAKE))
+ .isEqualTo("INFO_MAKE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_MODEL))
+ .isEqualTo("INFO_MODEL");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_MODEL_YEAR))
+ .isEqualTo("INFO_MODEL_YEAR");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_MULTI_EV_PORT_LOCATIONS))
+ .isEqualTo("INFO_MULTI_EV_PORT_LOCATIONS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.INFO_VIN))
+ .isEqualTo("INFO_VIN");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PERF_ODOMETER))
+ .isEqualTo("PERF_ODOMETER");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PERF_REAR_STEERING_ANGLE))
+ .isEqualTo("PERF_REAR_STEERING_ANGLE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PERF_STEERING_ANGLE))
+ .isEqualTo("PERF_STEERING_ANGLE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PERF_VEHICLE_SPEED))
+ .isEqualTo("PERF_VEHICLE_SPEED");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PERF_VEHICLE_SPEED_DISPLAY))
+ .isEqualTo("PERF_VEHICLE_SPEED_DISPLAY");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ENGINE_COOLANT_TEMP))
+ .isEqualTo("ENGINE_COOLANT_TEMP");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ENGINE_OIL_LEVEL))
+ .isEqualTo("ENGINE_OIL_LEVEL");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ENGINE_OIL_TEMP))
+ .isEqualTo("ENGINE_OIL_TEMP");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ENGINE_RPM))
+ .isEqualTo("ENGINE_RPM");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.WHEEL_TICK))
+ .isEqualTo("WHEEL_TICK");
+ assertThat(VehiclePropertyIds.toString(
+ VehiclePropertyIds.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME))
+ .isEqualTo("FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FUEL_DOOR_OPEN))
+ .isEqualTo("FUEL_DOOR_OPEN");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FUEL_LEVEL))
+ .isEqualTo("FUEL_LEVEL");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FUEL_LEVEL_LOW))
+ .isEqualTo("FUEL_LEVEL_LOW");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FUEL_VOLUME_DISPLAY_UNITS))
+ .isEqualTo("FUEL_VOLUME_DISPLAY_UNITS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.EV_BATTERY_DISPLAY_UNITS))
+ .isEqualTo("EV_BATTERY_DISPLAY_UNITS");
+ assertThat(VehiclePropertyIds.toString(
+ VehiclePropertyIds.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE))
+ .isEqualTo("EV_BATTERY_INSTANTANEOUS_CHARGE_RATE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.EV_BATTERY_LEVEL))
+ .isEqualTo("EV_BATTERY_LEVEL");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.EV_CHARGE_PORT_CONNECTED))
+ .isEqualTo("EV_CHARGE_PORT_CONNECTED");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.EV_CHARGE_PORT_OPEN))
+ .isEqualTo("EV_CHARGE_PORT_OPEN");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.RANGE_REMAINING))
+ .isEqualTo("RANGE_REMAINING");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.TIRE_PRESSURE)).
+ isEqualTo("TIRE_PRESSURE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.TIRE_PRESSURE_DISPLAY_UNITS))
+ .isEqualTo("TIRE_PRESSURE_DISPLAY_UNITS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.GEAR_SELECTION))
+ .isEqualTo("GEAR_SELECTION");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.CURRENT_GEAR))
+ .isEqualTo("CURRENT_GEAR");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PARKING_BRAKE_ON))
+ .isEqualTo("PARKING_BRAKE_ON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.PARKING_BRAKE_AUTO_APPLY))
+ .isEqualTo("PARKING_BRAKE_AUTO_APPLY");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.NIGHT_MODE))
+ .isEqualTo("NIGHT_MODE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.TURN_SIGNAL_STATE))
+ .isEqualTo("TURN_SIGNAL_STATE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ABS_ACTIVE))
+ .isEqualTo("ABS_ACTIVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.TRACTION_CONTROL_ACTIVE))
+ .isEqualTo("TRACTION_CONTROL_ACTIVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_AC_ON))
+ .isEqualTo("HVAC_AC_ON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_ACTUAL_FAN_SPEED_RPM))
+ .isEqualTo("HVAC_ACTUAL_FAN_SPEED_RPM");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_AUTO_ON))
+ .isEqualTo("HVAC_AUTO_ON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_AUTO_RECIRC_ON))
+ .isEqualTo("HVAC_AUTO_RECIRC_ON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_DEFROSTER))
+ .isEqualTo("HVAC_DEFROSTER");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_DUAL_ON))
+ .isEqualTo("HVAC_DUAL_ON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_FAN_DIRECTION))
+ .isEqualTo("HVAC_FAN_DIRECTION");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE))
+ .isEqualTo("HVAC_FAN_DIRECTION_AVAILABLE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_FAN_SPEED))
+ .isEqualTo("HVAC_FAN_SPEED");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_MAX_AC_ON))
+ .isEqualTo("HVAC_MAX_AC_ON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_MAX_DEFROST_ON))
+ .isEqualTo("HVAC_MAX_DEFROST_ON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_POWER_ON))
+ .isEqualTo("HVAC_POWER_ON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_RECIRC_ON))
+ .isEqualTo("HVAC_RECIRC_ON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_SEAT_TEMPERATURE))
+ .isEqualTo("HVAC_SEAT_TEMPERATURE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_SEAT_VENTILATION))
+ .isEqualTo("HVAC_SEAT_VENTILATION");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_SIDE_MIRROR_HEAT))
+ .isEqualTo("HVAC_SIDE_MIRROR_HEAT");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_STEERING_WHEEL_HEAT))
+ .isEqualTo("HVAC_STEERING_WHEEL_HEAT");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_TEMPERATURE_CURRENT))
+ .isEqualTo("HVAC_TEMPERATURE_CURRENT");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS))
+ .isEqualTo("HVAC_TEMPERATURE_DISPLAY_UNITS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_TEMPERATURE_SET))
+ .isEqualTo("HVAC_TEMPERATURE_SET");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.DISTANCE_DISPLAY_UNITS))
+ .isEqualTo("DISTANCE_DISPLAY_UNITS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.ENV_OUTSIDE_TEMPERATURE))
+ .isEqualTo("ENV_OUTSIDE_TEMPERATURE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.AP_POWER_BOOTUP_REASON))
+ .isEqualTo("AP_POWER_BOOTUP_REASON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.AP_POWER_STATE_REPORT))
+ .isEqualTo("AP_POWER_STATE_REPORT");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.AP_POWER_STATE_REQ))
+ .isEqualTo("AP_POWER_STATE_REQ");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.DISPLAY_BRIGHTNESS))
+ .isEqualTo("DISPLAY_BRIGHTNESS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HW_KEY_INPUT))
+ .isEqualTo("HW_KEY_INPUT");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.DOOR_LOCK))
+ .isEqualTo("DOOR_LOCK");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.DOOR_MOVE))
+ .isEqualTo("DOOR_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.DOOR_POS))
+ .isEqualTo("DOOR_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_FOLD))
+ .isEqualTo("MIRROR_FOLD");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_LOCK))
+ .isEqualTo("MIRROR_LOCK");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_Y_MOVE))
+ .isEqualTo("MIRROR_Y_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_Y_POS))
+ .isEqualTo("MIRROR_Y_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_Z_MOVE))
+ .isEqualTo("MIRROR_Z_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.MIRROR_Z_POS))
+ .isEqualTo("MIRROR_Z_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_MOVE))
+ .isEqualTo("SEAT_BACKREST_ANGLE_1_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_POS))
+ .isEqualTo("SEAT_BACKREST_ANGLE_1_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_MOVE))
+ .isEqualTo("SEAT_BACKREST_ANGLE_2_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_POS))
+ .isEqualTo("SEAT_BACKREST_ANGLE_2_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BELT_BUCKLED))
+ .isEqualTo("SEAT_BELT_BUCKLED");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BELT_HEIGHT_MOVE))
+ .isEqualTo("SEAT_BELT_HEIGHT_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_BELT_HEIGHT_POS))
+ .isEqualTo("SEAT_BELT_HEIGHT_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_DEPTH_MOVE))
+ .isEqualTo("SEAT_DEPTH_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_DEPTH_POS))
+ .isEqualTo("SEAT_DEPTH_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_FORE_AFT_MOVE))
+ .isEqualTo("SEAT_FORE_AFT_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_FORE_AFT_POS))
+ .isEqualTo("SEAT_FORE_AFT_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_ANGLE_MOVE))
+ .isEqualTo("SEAT_HEADREST_ANGLE_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_ANGLE_POS))
+ .isEqualTo("SEAT_HEADREST_ANGLE_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_MOVE))
+ .isEqualTo("SEAT_HEADREST_FORE_AFT_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_POS))
+ .isEqualTo("SEAT_HEADREST_FORE_AFT_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_HEIGHT_MOVE))
+ .isEqualTo("SEAT_HEADREST_HEIGHT_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEADREST_HEIGHT_POS))
+ .isEqualTo("SEAT_HEADREST_HEIGHT_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEIGHT_MOVE))
+ .isEqualTo("SEAT_HEIGHT_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_HEIGHT_POS))
+ .isEqualTo("SEAT_HEIGHT_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_MOVE))
+ .isEqualTo("SEAT_LUMBAR_FORE_AFT_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_POS))
+ .isEqualTo("SEAT_LUMBAR_FORE_AFT_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_MOVE))
+ .isEqualTo("SEAT_LUMBAR_SIDE_SUPPORT_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_POS))
+ .isEqualTo("SEAT_LUMBAR_SIDE_SUPPORT_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_MEMORY_SELECT))
+ .isEqualTo("SEAT_MEMORY_SELECT");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_MEMORY_SET))
+ .isEqualTo("SEAT_MEMORY_SET");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_OCCUPANCY))
+ .isEqualTo("SEAT_OCCUPANCY");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_TILT_MOVE))
+ .isEqualTo("SEAT_TILT_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.SEAT_TILT_POS))
+ .isEqualTo("SEAT_TILT_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.WINDOW_LOCK))
+ .isEqualTo("WINDOW_LOCK");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.WINDOW_MOVE))
+ .isEqualTo("WINDOW_MOVE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.WINDOW_POS))
+ .isEqualTo("WINDOW_POS");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.VEHICLE_MAP_SERVICE))
+ .isEqualTo("VEHICLE_MAP_SERVICE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.OBD2_FREEZE_FRAME))
+ .isEqualTo("OBD2_FREEZE_FRAME");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.OBD2_FREEZE_FRAME_CLEAR))
+ .isEqualTo("OBD2_FREEZE_FRAME_CLEAR");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.OBD2_FREEZE_FRAME_INFO))
+ .isEqualTo("OBD2_FREEZE_FRAME_INFO");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.OBD2_LIVE_FRAME))
+ .isEqualTo("OBD2_LIVE_FRAME");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HEADLIGHTS_STATE))
+ .isEqualTo("HEADLIGHTS_STATE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HEADLIGHTS_SWITCH))
+ .isEqualTo("HEADLIGHTS_SWITCH");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HIGH_BEAM_LIGHTS_STATE))
+ .isEqualTo("HIGH_BEAM_LIGHTS_STATE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HIGH_BEAM_LIGHTS_SWITCH))
+ .isEqualTo("HIGH_BEAM_LIGHTS_SWITCH");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FOG_LIGHTS_STATE))
+ .isEqualTo("FOG_LIGHTS_STATE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.FOG_LIGHTS_SWITCH))
+ .isEqualTo("FOG_LIGHTS_SWITCH");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HAZARD_LIGHTS_STATE))
+ .isEqualTo("HAZARD_LIGHTS_STATE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HAZARD_LIGHTS_SWITCH))
+ .isEqualTo("HAZARD_LIGHTS_SWITCH");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.CABIN_LIGHTS_STATE))
+ .isEqualTo("CABIN_LIGHTS_STATE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.CABIN_LIGHTS_SWITCH))
+ .isEqualTo("CABIN_LIGHTS_SWITCH");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.READING_LIGHTS_STATE))
+ .isEqualTo("READING_LIGHTS_STATE");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.READING_LIGHTS_SWITCH))
+ .isEqualTo("READING_LIGHTS_SWITCH");
+ assertThat(VehiclePropertyIds.toString(3)).isEqualTo("0x3");
+ assertThat(VehiclePropertyIds.toString(12)).isEqualTo("0xc");
+ }
+
+ /**
+ * Test if all system properties have a mapped string value.
+ */
+ @Test
+ public void testAllPropertiesAreMappedInToString() {
+ List<Integer> systemProperties = getIntegersFromDataEnums(VehiclePropertyIds.class);
+ for (int propertyId : systemProperties) {
+ String propertyString = VehiclePropertyIds.toString(propertyId);
+ assertThat(propertyString.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/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java b/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
index 46df40e..d7af7a8 100644
--- a/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
@@ -27,6 +27,10 @@
import static android.content.pm.PackageManager.INSTALL_REASON_POLICY;
import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.INSTALL_REASON_USER;
+import static android.content.pm.PackageManager.INSTALL_SCENARIO_BULK;
+import static android.content.pm.PackageManager.INSTALL_SCENARIO_BULK_SECONDARY;
+import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
+import static android.content.pm.PackageManager.INSTALL_SCENARIO_FAST;
import static com.google.common.truth.Truth.assertThat;
@@ -83,6 +87,8 @@
@Parameterized.Parameter(9)
public Optional<Integer> installReason;
@Parameterized.Parameter(10)
+ public Optional<Integer> installScenario;
+ @Parameterized.Parameter(11)
public boolean expectFailure;
private PackageInstaller mInstaller = InstrumentationRegistry.getInstrumentation()
@@ -155,7 +161,10 @@
/*installReason*/
{{INSTALL_REASON_UNKNOWN, INSTALL_REASON_POLICY, INSTALL_REASON_DEVICE_RESTORE,
INSTALL_REASON_DEVICE_SETUP, INSTALL_REASON_USER,
- /* parame is not verified */ 0xfff}, {}}};
+ /* parame is not verified */ 0xfff}, {}},
+ /*installScenario*/
+ {{INSTALL_SCENARIO_DEFAULT, INSTALL_SCENARIO_FAST, INSTALL_SCENARIO_BULK,
+ INSTALL_SCENARIO_BULK_SECONDARY}, {}}};
ArrayList<Object[]> allTestParams = new ArrayList<>();
@@ -207,7 +216,7 @@
+ " appPackageName=" + appPackageName + " appIcon=" + appIcon + " appLabel="
+ appLabel + " originatingUri=" + originatingUri + " originatingUid="
+ originatingUid + " referredUri=" + referredUri + " installReason=" + installReason
- + " expectFailure=" + expectFailure);
+ + " installScenario=" + installScenario + " expectFailure=" + expectFailure);
SessionParams params = new SessionParams(mode.get());
installLocation.ifPresent(params::setInstallLocation);
@@ -219,6 +228,7 @@
originatingUid.ifPresent(params::setOriginatingUid);
referredUri.ifPresent(params::setReferrerUri);
installReason.ifPresent(params::setInstallReason);
+ installScenario.ifPresent(params::setInstallScenario);
int sessionId;
try {
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
index 62fadd8..70e0735 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
@@ -76,30 +76,42 @@
};
int systemColors[] = {
- android.R.color.system_main_0,
- android.R.color.system_main_50,
- android.R.color.system_main_100,
- android.R.color.system_main_200,
- android.R.color.system_main_300,
- android.R.color.system_main_400,
- android.R.color.system_main_500,
- android.R.color.system_main_600,
- android.R.color.system_main_700,
- android.R.color.system_main_800,
- android.R.color.system_main_900,
- android.R.color.system_main_1000,
- android.R.color.system_accent_0,
- android.R.color.system_accent_50,
- android.R.color.system_accent_100,
- android.R.color.system_accent_200,
- android.R.color.system_accent_300,
- android.R.color.system_accent_400,
- android.R.color.system_accent_500,
- android.R.color.system_accent_600,
- android.R.color.system_accent_700,
- android.R.color.system_accent_800,
- android.R.color.system_accent_900,
- android.R.color.system_accent_1000,
+ android.R.color.system_primary_0,
+ android.R.color.system_primary_50,
+ android.R.color.system_primary_100,
+ android.R.color.system_primary_200,
+ android.R.color.system_primary_300,
+ android.R.color.system_primary_400,
+ android.R.color.system_primary_500,
+ android.R.color.system_primary_600,
+ android.R.color.system_primary_700,
+ android.R.color.system_primary_800,
+ android.R.color.system_primary_900,
+ android.R.color.system_primary_1000,
+ android.R.color.system_secondary_0,
+ android.R.color.system_secondary_50,
+ android.R.color.system_secondary_100,
+ android.R.color.system_secondary_200,
+ android.R.color.system_secondary_300,
+ android.R.color.system_secondary_400,
+ android.R.color.system_secondary_500,
+ android.R.color.system_secondary_600,
+ android.R.color.system_secondary_700,
+ android.R.color.system_secondary_800,
+ android.R.color.system_secondary_900,
+ android.R.color.system_secondary_1000,
+ android.R.color.system_neutral_0,
+ android.R.color.system_neutral_50,
+ android.R.color.system_neutral_100,
+ android.R.color.system_neutral_200,
+ android.R.color.system_neutral_300,
+ android.R.color.system_neutral_400,
+ android.R.color.system_neutral_500,
+ android.R.color.system_neutral_600,
+ android.R.color.system_neutral_700,
+ android.R.color.system_neutral_800,
+ android.R.color.system_neutral_900,
+ android.R.color.system_neutral_1000,
};
List<Integer> expectedColorStateLists = Arrays.asList(
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
index 5f0697c..5e0d4f6 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
@@ -47,29 +47,38 @@
@Test
public void testShades0and1000() {
final Context context = getInstrumentation().getTargetContext();
- final int system0 = context.getColor(R.color.system_main_0);
- final int system1000 = context.getColor(R.color.system_main_1000);
- final int accent0 = context.getColor(R.color.system_accent_0);
- final int accent1000 = context.getColor(R.color.system_accent_1000);
- assertColor(system0, Color.WHITE);
- assertColor(system1000, Color.BLACK);
- assertColor(accent0, Color.WHITE);
- assertColor(accent1000, Color.BLACK);
+ final int primary0 = context.getColor(R.color.system_primary_0);
+ final int primary1000 = context.getColor(R.color.system_primary_1000);
+ final int secondary0 = context.getColor(R.color.system_secondary_0);
+ final int secondary1000 = context.getColor(R.color.system_secondary_1000);
+ final int neutral0 = context.getColor(R.color.system_neutral_0);
+ final int neutral1000 = context.getColor(R.color.system_neutral_1000);
+ assertColor(primary0, Color.WHITE);
+ assertColor(primary1000, Color.BLACK);
+ assertColor(secondary0, Color.WHITE);
+ assertColor(secondary1000, Color.BLACK);
+ assertColor(neutral0, Color.WHITE);
+ assertColor(neutral1000, Color.BLACK);
}
@Test
public void testAllColorsBelongToSameFamily() {
final Context context = getInstrumentation().getTargetContext();
- final int[] mainColors = getAllMainColors(context);
- final int[] accentColors = getAllAccentColors(context);
+ final int[] primaryColors = getAllPrimaryColors(context);
+ final int[] secondaryColors = getAllSecondaryColors(context);
+ final int[] neutralColors = getAllNeutralColors(context);
- for (int i = 2; i < mainColors.length - 1; i++) {
- assertWithMessage("Main color " + Integer.toHexString((mainColors[i - 1]))
- + " has different chroma compared to " + Integer.toHexString(mainColors[i]))
- .that(similarChroma(mainColors[i - 1], mainColors[i])).isTrue();
- assertWithMessage("Accent color " + Integer.toHexString((accentColors[i - 1]))
- + " has different chroma compared to " + Integer.toHexString(accentColors[i]))
- .that(similarChroma(accentColors[i - 1], accentColors[i])).isTrue();
+ for (int i = 2; i < primaryColors.length - 1; i++) {
+ assertWithMessage("Primary color " + Integer.toHexString((primaryColors[i - 1]))
+ + " has different chroma compared to " + Integer.toHexString(primaryColors[i]))
+ .that(similarChroma(primaryColors[i - 1], primaryColors[i])).isTrue();
+ assertWithMessage("Secondary color " + Integer.toHexString((secondaryColors[i - 1]))
+ + " has different chroma compared to " + Integer.toHexString(
+ secondaryColors[i]))
+ .that(similarChroma(secondaryColors[i - 1], secondaryColors[i])).isTrue();
+ assertWithMessage("Neutral color " + Integer.toHexString((neutralColors[i - 1]))
+ + " has different chroma compared to " + Integer.toHexString(neutralColors[i]))
+ .that(similarChroma(neutralColors[i - 1], neutralColors[i])).isTrue();
}
}
@@ -99,27 +108,34 @@
@Test
public void testColorsMatchExpectedLuminosity() {
final Context context = getInstrumentation().getTargetContext();
- final int[] mainColors = getAllMainColors(context);
- final int[] accentColors = getAllAccentColors(context);
+ final int[] primaryColors = getAllPrimaryColors(context);
+ final int[] secondaryColors = getAllSecondaryColors(context);
+ final int[] neutralColors = getAllNeutralColors(context);
- final double[] labMain = new double[3];
- final double[] labAccent = new double[3];
+ final double[] labPrimary = new double[3];
+ final double[] labSecondary = new double[3];
+ final double[] labNeutral = new double[3];
final double[] expectedL = {100, 95, 90, 80, 70, 60, 49, 40, 30, 20, 10, 0};
- for (int i = 0; i < mainColors.length; i++) {
- ColorUtils.RGBToLAB(Color.red(mainColors[i]), Color.green(mainColors[i]),
- Color.blue(mainColors[i]), labMain);
- ColorUtils.RGBToLAB(Color.red(accentColors[i]), Color.green(accentColors[i]),
- Color.blue(accentColors[i]), labAccent);
+ for (int i = 0; i < primaryColors.length; i++) {
+ ColorUtils.RGBToLAB(Color.red(primaryColors[i]), Color.green(primaryColors[i]),
+ Color.blue(primaryColors[i]), labPrimary);
+ ColorUtils.RGBToLAB(Color.red(secondaryColors[i]), Color.green(secondaryColors[i]),
+ Color.blue(secondaryColors[i]), labSecondary);
+ ColorUtils.RGBToLAB(Color.red(neutralColors[i]), Color.green(neutralColors[i]),
+ Color.blue(neutralColors[i]), labNeutral);
// Colors in the same palette should vary mostly in L, decreasing lightness as we move
// across the palette.
- assertWithMessage("Color " + Integer.toHexString((mainColors[i]))
+ assertWithMessage("Color " + Integer.toHexString((primaryColors[i]))
+ " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
- .that(labMain[0]).isWithin(3).of(expectedL[i]);
- assertWithMessage("Color " + Integer.toHexString((accentColors[i]))
+ .that(labPrimary[0]).isWithin(3).of(expectedL[i]);
+ assertWithMessage("Color " + Integer.toHexString((secondaryColors[i]))
+ " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
- .that(labAccent[0]).isWithin(3).of(expectedL[i]);
+ .that(labSecondary[0]).isWithin(3).of(expectedL[i]);
+ assertWithMessage("Color " + Integer.toHexString((neutralColors[i]))
+ + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
+ .that(labNeutral[0]).isWithin(3).of(expectedL[i]);
}
}
@@ -135,10 +151,11 @@
new Pair<>(300, 700), new Pair<>(400, 800), new Pair<>(500, 900),
new Pair<>(600, 1000));
- final int[] mainColors = getAllMainColors(context);
- final int[] accentColors = getAllAccentColors(context);
+ final int[] primaryColors = getAllPrimaryColors(context);
+ final int[] secondaryColors = getAllSecondaryColors(context);
+ final int[] neutralColors = getAllNeutralColors(context);
- for (int[] palette: Arrays.asList(mainColors, accentColors)) {
+ for (int[] palette: Arrays.asList(primaryColors, secondaryColors, neutralColors)) {
for (Pair<Integer, Integer> shades: atLeast4dot5) {
final int background = palette[shadeToArrayIndex(shades.first)];
final int foreground = palette[shadeToArrayIndex(shades.second)];
@@ -166,8 +183,9 @@
*
* @param shade Shade from 0 to 1000.
* @return index in array
- * @see getAllMainColors(Context)
- * @see getAllAccentColors(Context)
+ * @see #getAllPrimaryColors(Context)
+ * @see #getAllSecondaryColors(Context)
+ * @see #getAllNeutralColors(Context)
*/
private int shadeToArrayIndex(int shade) {
if (shade == 0) {
@@ -185,37 +203,54 @@
observed, expected);
}
- private int[] getAllMainColors(Context context) {
+ private int[] getAllPrimaryColors(Context context) {
final int[] colors = new int[12];
- colors[0] = context.getColor(R.color.system_main_0);
- colors[1] = context.getColor(R.color.system_main_50);
- colors[2] = context.getColor(R.color.system_main_100);
- colors[3] = context.getColor(R.color.system_main_200);
- colors[4] = context.getColor(R.color.system_main_300);
- colors[5] = context.getColor(R.color.system_main_400);
- colors[6] = context.getColor(R.color.system_main_500);
- colors[7] = context.getColor(R.color.system_main_600);
- colors[8] = context.getColor(R.color.system_main_700);
- colors[9] = context.getColor(R.color.system_main_800);
- colors[10] = context.getColor(R.color.system_main_900);
- colors[11] = context.getColor(R.color.system_main_1000);
+ colors[0] = context.getColor(R.color.system_primary_0);
+ colors[1] = context.getColor(R.color.system_primary_50);
+ colors[2] = context.getColor(R.color.system_primary_100);
+ colors[3] = context.getColor(R.color.system_primary_200);
+ colors[4] = context.getColor(R.color.system_primary_300);
+ colors[5] = context.getColor(R.color.system_primary_400);
+ colors[6] = context.getColor(R.color.system_primary_500);
+ colors[7] = context.getColor(R.color.system_primary_600);
+ colors[8] = context.getColor(R.color.system_primary_700);
+ colors[9] = context.getColor(R.color.system_primary_800);
+ colors[10] = context.getColor(R.color.system_primary_900);
+ colors[11] = context.getColor(R.color.system_primary_1000);
return colors;
}
- private int[] getAllAccentColors(Context context) {
+ private int[] getAllSecondaryColors(Context context) {
final int[] colors = new int[12];
- colors[0] = context.getColor(R.color.system_accent_0);
- colors[1] = context.getColor(R.color.system_accent_50);
- colors[2] = context.getColor(R.color.system_accent_100);
- colors[3] = context.getColor(R.color.system_accent_200);
- colors[4] = context.getColor(R.color.system_accent_300);
- colors[5] = context.getColor(R.color.system_accent_400);
- colors[6] = context.getColor(R.color.system_accent_500);
- colors[7] = context.getColor(R.color.system_accent_600);
- colors[8] = context.getColor(R.color.system_accent_700);
- colors[9] = context.getColor(R.color.system_accent_800);
- colors[10] = context.getColor(R.color.system_accent_900);
- colors[11] = context.getColor(R.color.system_accent_1000);
+ colors[0] = context.getColor(R.color.system_secondary_0);
+ colors[1] = context.getColor(R.color.system_secondary_50);
+ colors[2] = context.getColor(R.color.system_secondary_100);
+ colors[3] = context.getColor(R.color.system_secondary_200);
+ colors[4] = context.getColor(R.color.system_secondary_300);
+ colors[5] = context.getColor(R.color.system_secondary_400);
+ colors[6] = context.getColor(R.color.system_secondary_500);
+ colors[7] = context.getColor(R.color.system_secondary_600);
+ colors[8] = context.getColor(R.color.system_secondary_700);
+ colors[9] = context.getColor(R.color.system_secondary_800);
+ colors[10] = context.getColor(R.color.system_secondary_900);
+ colors[11] = context.getColor(R.color.system_secondary_1000);
+ return colors;
+ }
+
+ private int[] getAllNeutralColors(Context context) {
+ final int[] colors = new int[12];
+ colors[0] = context.getColor(R.color.system_neutral_0);
+ colors[1] = context.getColor(R.color.system_neutral_50);
+ colors[2] = context.getColor(R.color.system_neutral_100);
+ colors[3] = context.getColor(R.color.system_neutral_200);
+ colors[4] = context.getColor(R.color.system_neutral_300);
+ colors[5] = context.getColor(R.color.system_neutral_400);
+ colors[6] = context.getColor(R.color.system_neutral_500);
+ colors[7] = context.getColor(R.color.system_neutral_600);
+ colors[8] = context.getColor(R.color.system_neutral_700);
+ colors[9] = context.getColor(R.color.system_neutral_800);
+ colors[10] = context.getColor(R.color.system_neutral_900);
+ colors[11] = context.getColor(R.color.system_neutral_1000);
return colors;
}
}
diff --git a/tests/tests/hardware/res/raw/sony_dualshock3_usb_lighttests.json b/tests/tests/hardware/res/raw/sony_dualshock3_usb_lighttests.json
new file mode 100644
index 0000000..ec88ed8
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock3_usb_lighttests.json
@@ -0,0 +1,19 @@
+// Refer to kernel sony_set_leds() in drivers/hid/hid-sony.c
+[
+ {
+ "id": 1,
+ "lightType": "INPUT_PLAYER_ID",
+ "lightName": "sony",
+ "lightColor": 0x00000000,
+ "lightPlayerId": 4,
+ "hidEventType": "UHID_SET_REPORT",
+ "ledsHidOutput": // LED Bitmask data index of HID SET_REPORT packet.
+ [ { "index": 10, "data": 16 }
+ ],
+ // Dualshock 3 USB need to send a HID report to start working.
+ "report": [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0xee, 0x12, 0x00, 0x00, 0x00, 0x00, 0x12, 0xd6, 0x77, 0x00, 0x40,
+ 0x01, 0xfa, 0x02, 0x62, 0x02, 0x33, 0x01, 0xeb]
+ }
+]
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_lighttests.json b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_lighttests.json
new file mode 100644
index 0000000..26e1ebb
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_lighttests.json
@@ -0,0 +1,48 @@
+// Refer to kernel sony_set_leds() in drivers/hid/hid-sony.c
+[
+ {
+ "id": 1,
+ "lightType": "INPUT_RGB",
+ "lightName": "RGB",
+ "report": [],
+ "lightColor": 0xff446688,
+ "lightPlayerId": 0,
+ "hidEventType": "UHID_OUTPUT",
+ "ledsHidOutput": // RGB leds brightness
+ [ { "index": 8, "data": 0x44 },
+ { "index": 9, "data": 0x66 },
+ { "index": 10, "data": 0x88 }
+ ]
+ },
+
+ {
+ "id": 1,
+ "lightType": "INPUT_RGB",
+ "lightName": "RGB",
+ "report": [],
+ "lightColor": 0x7f446688,
+ "lightPlayerId": 0,
+ "hidEventType": "UHID_OUTPUT",
+ "ledsHidOutput": // RGB leds brightness
+ [ { "index": 8, "data": 0x22 },
+ { "index": 9, "data": 0x33 },
+ { "index": 10, "data": 0x44 }
+ ]
+ },
+
+ {
+ "id": 1,
+ "lightType": "INPUT_RGB",
+ "lightName": "RGB",
+ "report": [],
+ "lightColor": 0x00000000,
+ "lightPlayerId": 0,
+ "hidEventType": "UHID_OUTPUT",
+ "ledsHidOutput": // RGB leds brightness
+ [ { "index": 8, "data": 0x0 },
+ { "index": 9, "data": 0x0 },
+ { "index": 10, "data": 0x0 }
+ ]
+ }
+
+]
diff --git a/tests/tests/hardware/src/android/hardware/cts/SecurityModelFeatureTest.java b/tests/tests/hardware/src/android/hardware/cts/SecurityModelFeatureTest.java
new file mode 100644
index 0000000..8c562c5
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/SecurityModelFeatureTest.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.hardware.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that devices correctly declare the
+ * {@link PackageManager#FEATURE_SECURITY_MODEL_COMPATIBLE} feature.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SecurityModelFeatureTest {
+ private static final String ERROR_MSG = "Expected system feature missing. "
+ + "The device must declare: " + PackageManager.FEATURE_SECURITY_MODEL_COMPATIBLE;
+ private PackageManager mPackageManager;
+ private boolean mHasSecurityFeature = false;
+
+ @Before
+ public void setUp() throws Exception {
+ mPackageManager = InstrumentationRegistry.getTargetContext().getPackageManager();
+ mHasSecurityFeature =
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_SECURITY_MODEL_COMPATIBLE);
+ }
+
+ @Test
+ @CddTest(requirement = "2.3.5/T-0-1")
+ public void testTv() {
+ assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
+ assertTrue(ERROR_MSG, mHasSecurityFeature);
+ }
+
+ @Test
+ @CddTest(requirement = "2.4.5/W-0-1")
+ public void testWatch() {
+ assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH));
+ assertTrue(ERROR_MSG, mHasSecurityFeature);
+ }
+
+ @Test
+ @CddTest(requirement = "2.5.5/A-0-1")
+ public void testAuto() {
+ assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+ assertTrue(ERROR_MSG, mHasSecurityFeature);
+ }
+
+ // Handheld & tablet tested via CTS Verifier
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
index df05c15..842934f 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
@@ -16,6 +16,10 @@
package android.hardware.input.cts.tests;
+import static android.hardware.lights.LightsRequest.Builder;
+
+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;
@@ -27,6 +31,9 @@
import android.hardware.Battery;
import android.hardware.input.InputManager;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
import android.os.SystemClock;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -36,6 +43,7 @@
import com.android.cts.input.HidBatteryTestData;
import com.android.cts.input.HidDevice;
+import com.android.cts.input.HidLightTestData;
import com.android.cts.input.HidResultData;
import com.android.cts.input.HidTestData;
import com.android.cts.input.HidVibratorTestData;
@@ -45,7 +53,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -71,52 +78,64 @@
mRegisterResourceId = registerResourceId;
}
- /**
- * Get a vibrator from input device with specified Vendor Id and Product Id
- * from device registration command.
- * @return Vibrator object in specified InputDevice
- */
- private Vibrator getVibrator() {
+ /** Check if input device has specific capability */
+ interface Capability {
+ boolean check(InputDevice inputDevice);
+ }
+
+ /** Gets an input device with specific capability */
+ private InputDevice getInputDevice(Capability capability) {
final InputManager inputManager =
mInstrumentation.getTargetContext().getSystemService(InputManager.class);
final int[] inputDeviceIds = inputManager.getInputDeviceIds();
- final int vid = mParser.readVendorId(mRegisterResourceId);
- final int pid = mParser.readProductId(mRegisterResourceId);
-
for (int inputDeviceId : inputDeviceIds) {
final InputDevice inputDevice = inputManager.getInputDevice(inputDeviceId);
- Vibrator vibrator = inputDevice.getVibrator();
- if (vibrator.hasVibrator() && inputDevice.getVendorId() == vid
- && inputDevice.getProductId() == pid) {
- return vibrator;
+ if (inputDevice.getVendorId() == mVid && inputDevice.getProductId() == mPid
+ && capability.check(inputDevice)) {
+ return inputDevice;
}
}
- fail("getVibrator() returns null");
return null;
}
/**
- * Get a battery from input device with specified Vendor Id and Product Id
+ * Gets a vibrator from input device with specified Vendor Id and Product Id
+ * from device registration command.
+ * @return Vibrator object in specified InputDevice
+ */
+ private Vibrator getVibrator() {
+ InputDevice inputDevice = getInputDevice((d) -> d.getVibrator().hasVibrator());
+ if (inputDevice == null) {
+ fail("Failed to find test device with vibrator");
+ }
+ return inputDevice.getVibrator();
+ }
+
+ /**
+ * Gets a battery from input device with specified Vendor Id and Product Id
* from device registration command.
* @return Battery object in specified InputDevice
*/
private Battery getBattery() {
- final InputManager inputManager =
- mInstrumentation.getTargetContext().getSystemService(InputManager.class);
- final int[] inputDeviceIds = inputManager.getInputDeviceIds();
- final int vid = mParser.readVendorId(mRegisterResourceId);
- final int pid = mParser.readProductId(mRegisterResourceId);
-
- for (int inputDeviceId : inputDeviceIds) {
- final InputDevice inputDevice = inputManager.getInputDevice(inputDeviceId);
- Battery battery = inputDevice.getBattery();
- if (battery.hasBattery() && inputDevice.getVendorId() == vid
- && inputDevice.getProductId() == pid) {
- return battery;
- }
+ InputDevice inputDevice = getInputDevice((d) -> d.getBattery().hasBattery());
+ if (inputDevice == null) {
+ fail("Failed to find test device with battery");
}
- fail("getBattery() returns null");
- return null;
+ return inputDevice.getBattery();
+ }
+
+ /**
+ * Gets a light manager object from input device with specified Vendor Id and Product Id
+ * from device registration command.
+ * @return LightsManager object in specified InputDevice
+ */
+ private LightsManager getLightsManager() {
+ InputDevice inputDevice = getInputDevice(
+ (d) -> !d.getLightsManager().getLights().isEmpty());
+ if (inputDevice == null) {
+ fail("Failed to find test device with light");
+ }
+ return inputDevice.getLightsManager();
}
@Override
@@ -152,7 +171,7 @@
}
}
- private boolean verifyReportData(HidVibratorTestData test, HidResultData result) {
+ private boolean verifyVibratorReportData(HidVibratorTestData test, HidResultData result) {
for (Map.Entry<Integer, Integer> entry : test.verifyMap.entrySet()) {
final int index = entry.getKey();
final int value = entry.getValue();
@@ -168,7 +187,7 @@
return ffLeft > 0 && ffRight > 0;
}
- public void testInputVibratorEvents(int resourceId) {
+ public void testInputVibratorEvents(int resourceId) throws Exception {
final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId);
for (HidVibratorTestData test : tests) {
@@ -213,28 +232,26 @@
while (vibrationCount < totalVibrations
&& SystemClock.elapsedRealtime() - startTime < timeoutMills) {
SystemClock.sleep(1000);
- try {
- results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT);
- if (results.size() < totalVibrations) {
- continue;
- }
- vibrationCount = 0;
- for (int i = 0; i < results.size(); i++) {
- HidResultData result = results.get(i);
- if (result.deviceId == mDeviceId && verifyReportData(test, result)) {
- int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
- int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
- Log.v(TAG, "eventId=" + result.eventId + " reportType="
- + result.reportType + " left=" + ffLeft + " right=" + ffRight);
- // Check the amplitudes of FF effect are expected.
- if (ffLeft == test.amplitudes.get(vibrationCount)
- && ffRight == test.amplitudes.get(vibrationCount)) {
- vibrationCount++;
- }
+
+ results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT);
+ if (results.size() < totalVibrations) {
+ continue;
+ }
+ vibrationCount = 0;
+ for (int i = 0; i < results.size(); i++) {
+ HidResultData result = results.get(i);
+ if (result.deviceId == mDeviceId
+ && verifyVibratorReportData(test, result)) {
+ int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
+ int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
+ Log.v(TAG, "eventId=" + result.eventId + " reportType="
+ + result.reportType + " left=" + ffLeft + " right=" + ffRight);
+ // Check the amplitudes of FF effect are expected.
+ if (ffLeft == test.amplitudes.get(vibrationCount)
+ && ffRight == test.amplitudes.get(vibrationCount)) {
+ vibrationCount++;
}
}
- } catch (IOException ex) {
- throw new RuntimeException("Could not get JSON results from HidDevice");
}
}
assertEquals(vibrationCount, totalVibrations);
@@ -247,7 +264,7 @@
}
}
- public void testInputVibratorManagerEvents(int resourceId) {
+ public void testInputVibratorManagerEvents(int resourceId) throws Exception {
final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId);
for (HidVibratorTestData test : tests) {
@@ -283,28 +300,26 @@
while (vibrationCount < totalVibrations
&& SystemClock.elapsedRealtime() - startTime < timeoutMills) {
SystemClock.sleep(1000);
- try {
- results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT);
- if (results.size() < totalVibrations) {
- continue;
- }
- vibrationCount = 0;
- for (int i = 0; i < results.size(); i++) {
- HidResultData result = results.get(i);
- if (result.deviceId == mDeviceId && verifyReportData(test, result)) {
- int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
- int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
- Log.v(TAG, "eventId=" + result.eventId + " reportType="
- + result.reportType + " left=" + ffLeft + " right=" + ffRight);
- // Check the amplitudes of FF effect are expected.
- if (ffLeft == test.amplitudes.get(vibrationCount)
- && ffRight == test.amplitudes.get(vibrationCount)) {
- vibrationCount++;
- }
+
+ results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT);
+ if (results.size() < totalVibrations) {
+ continue;
+ }
+ vibrationCount = 0;
+ for (int i = 0; i < results.size(); i++) {
+ HidResultData result = results.get(i);
+ if (result.deviceId == mDeviceId
+ && verifyVibratorReportData(test, result)) {
+ int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
+ int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
+ Log.v(TAG, "eventId=" + result.eventId + " reportType="
+ + result.reportType + " left=" + ffLeft + " right=" + ffRight);
+ // Check the amplitudes of FF effect are expected.
+ if (ffLeft == test.amplitudes.get(vibrationCount)
+ && ffRight == test.amplitudes.get(vibrationCount)) {
+ vibrationCount++;
}
}
- } catch (IOException ex) {
- throw new RuntimeException("Could not get JSON results from HidDevice");
}
}
assertEquals(vibrationCount, totalVibrations);
@@ -340,4 +355,65 @@
}
}
+ public void testInputLightsManager(int resourceId) throws Exception {
+ final LightsManager lightsManager = getLightsManager();
+ final List<Light> lights = lightsManager.getLights();
+
+ final List<HidLightTestData> tests = mParser.getHidLightTestData(resourceId);
+ for (HidLightTestData test : tests) {
+ Light light = null;
+ for (int i = 0; i < lights.size(); i++) {
+ if (lights.get(i).getType() == test.lightType
+ && test.lightName.equals(lights.get(i).getName())) {
+ light = lights.get(i);
+ }
+ }
+ assertNotNull("Light type " + test.lightType + " name " + test.lightName
+ + " does not exist", light);
+ try (LightsManager.LightsSession session = lightsManager.openSession()) {
+ // Can't set both player id and color in same LightState
+ assertFalse(test.lightColor > 0 && test.lightPlayerId > 0);
+ // Issue the session requests to turn single light on
+ if (test.lightPlayerId > 0) {
+ session.requestLights(new Builder()
+ .addLight(light, LightState.forPlayerId(test.lightPlayerId))
+ .build());
+ } else {
+ session.requestLights(new Builder()
+ .addLight(light, LightState.forColor(test.lightColor))
+ .build());
+ }
+ // Some devices (e.g. Sixaxis) defer sending output packets until they've seen at
+ // least one input packet.
+ if (!test.report.isEmpty()) {
+ mHidDevice.sendHidReport(test.report);
+ }
+ // Delay before sysfs node was updated.
+ SystemClock.sleep(200);
+ // Verify HID report data
+ List<HidResultData> results = mHidDevice.getResults(mDeviceId,
+ test.hidEventType);
+ assertFalse(results.isEmpty());
+ // We just check the last HID output to be expected.
+ HidResultData result = results.get(results.size() - 1);
+ for (Map.Entry<Integer, Integer> entry : test.expectedHidData.entrySet()) {
+ final int index = entry.getKey();
+ final int value = entry.getValue();
+ int actual = result.reportData[index] & 0xFF;
+ assertEquals("Led data index " + index, value, actual);
+
+ }
+
+ // Then the light state should be what we requested.
+ if (test.lightPlayerId > 0) {
+ assertThat(lightsManager.getLightState(light).getPlayerId())
+ .isEqualTo(test.lightPlayerId);
+ } else {
+ assertThat(lightsManager.getLightState(light).getColor())
+ .isEqualTo(test.lightColor);
+ }
+ }
+ }
+ }
+
}
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 a95f8c0..f4dc9b9 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
@@ -59,6 +59,8 @@
// Stores the name of the currently running test
protected String mCurrentTestCase;
private int mRegisterResourceId; // raw resource that contains json for registering a hid device
+ protected int mVid;
+ protected int mPid;
// State used for motion events
private int mLastButtonState;
@@ -79,6 +81,8 @@
mActivityRule.getActivity().clearUnhandleKeyCode();
mDecorView = mActivityRule.getActivity().getWindow().getDecorView();
mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+ mVid = mParser.readVendorId(mRegisterResourceId);
+ mPid = mParser.readProductId(mRegisterResourceId);
int deviceId = mParser.readDeviceId(mRegisterResourceId);
String registerCommand = mParser.readRegisterCommand(mRegisterResourceId);
setUpDevice(deviceId, mParser.readVendorId(mRegisterResourceId),
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java
index 6f4762f..dc53e74 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java
@@ -42,4 +42,9 @@
public void testAllMotions() {
testInputEvents(R.raw.sony_dualshock3_usb_motioneventtests);
}
+
+ @Test
+ public void testLights() throws Exception {
+ testInputLightsManager(R.raw.sony_dualshock3_usb_lighttests);
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
index 36c41f2..f8be924 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
@@ -44,7 +44,7 @@
}
@Test
- public void testVibrator() {
+ public void testVibrator() throws Exception {
testInputVibratorEvents(R.raw.sony_dualshock4_bluetooth_vibratortests);
}
@@ -59,4 +59,9 @@
testInputEvents(R.raw.sony_dualshock4_toucheventtests);
}
}
+
+ @Test
+ public void testLights() throws Exception {
+ testInputLightsManager(R.raw.sony_dualshock4_bluetooth_lighttests);
+ }
}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
index 5c83ab5..8a95e9c 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
@@ -44,7 +44,7 @@
}
@Test
- public void testVibrator() {
+ public void testVibrator() throws Exception {
testInputVibratorEvents(R.raw.sony_dualshock4_bluetooth_vibratortests);
}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
index e0f8d99..739ea36 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
@@ -49,7 +49,7 @@
}
@Test
- public void testVibrator() {
+ public void testVibrator() throws Exception {
testInputVibratorEvents(R.raw.sony_dualshock4_usb_vibratortests);
}
}
diff --git a/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java b/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java
index aca19ec..622f7b0 100755
--- a/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java
+++ b/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java
@@ -28,7 +28,6 @@
import android.hardware.lights.LightState;
import android.hardware.lights.LightsManager;
-import androidx.annotation.ColorInt;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -92,7 +91,7 @@
try (LightsManager.LightsSession session = mManager.openSession()) {
// When the session requests to turn a single light on:
session.requestLights(new Builder()
- .setLight(mLights.get(0), STATE_RED)
+ .addLight(mLights.get(0), STATE_RED)
.build());
// Then the light should turn on.
@@ -107,8 +106,8 @@
try (LightsManager.LightsSession session = mManager.openSession()) {
// When the session requests to turn two of the lights on:
session.requestLights(new Builder()
- .setLight(mLights.get(0), new LightState(0xffaaaaff))
- .setLight(mLights.get(1), new LightState(0xffbbbbff))
+ .addLight(mLights.get(0), new LightState(0xffaaaaff))
+ .addLight(mLights.get(1), new LightState(0xffbbbbff))
.build());
// Then both should turn on.
@@ -131,7 +130,7 @@
try (LightsManager.LightsSession session = mManager.openSession()) {
// When a session commits changes:
- session.requestLights(new Builder().setLight(mLights.get(0), STATE_TAN).build());
+ session.requestLights(new Builder().addLight(mLights.get(0), STATE_TAN).build());
// Then the light should turn on.
assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(ON_TAN);
@@ -150,8 +149,8 @@
LightsManager.LightsSession session2 = mManager.openSession()) {
// When session1 and session2 both request the same light:
- session1.requestLights(new Builder().setLight(mLights.get(0), STATE_TAN).build());
- session2.requestLights(new Builder().setLight(mLights.get(0), STATE_RED).build());
+ session1.requestLights(new Builder().addLight(mLights.get(0), STATE_TAN).build());
+ session2.requestLights(new Builder().addLight(mLights.get(0), STATE_RED).build());
// Then session1 should win because it was created first.
assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(ON_TAN);
@@ -173,7 +172,7 @@
try (LightsManager.LightsSession session = mManager.openSession()) {
// When the session turns a light on:
- session.requestLights(new Builder().setLight(mLights.get(0), STATE_RED).build());
+ session.requestLights(new Builder().addLight(mLights.get(0), STATE_RED).build());
// And then the session clears it again:
session.requestLights(new Builder().clearLight(mLights.get(0)).build());
// Then the light should turn back off.
diff --git a/tests/tests/icu/Android.bp b/tests/tests/icu/Android.bp
index df1f6d0..3899db8 100644
--- a/tests/tests/icu/Android.bp
+++ b/tests/tests/icu/Android.bp
@@ -36,8 +36,10 @@
defaults: ["cts_support_defaults"],
test_config: "CtsIcu4cTestCases.xml",
data: [
- ":cintltst",
- ":intltest",
+ ":cintltst32",
+ ":cintltst64",
+ ":intltest32",
+ ":intltest64",
":icu4c_test_data",
":ICU4CTestRunner",
],
diff --git a/tests/tests/icu/CtsIcu4cTestCases.xml b/tests/tests/icu/CtsIcu4cTestCases.xml
index acef467..c216b3a 100644
--- a/tests/tests/icu/CtsIcu4cTestCases.xml
+++ b/tests/tests/icu/CtsIcu4cTestCases.xml
@@ -17,17 +17,18 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
- <!-- Test single abi only due to a limitation in Soong http://b/141637587#comment28. -->
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <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.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
- <option name="push" value="icu.cintltst->/data/local/tmp/cintltst" />
+ <option name="append-bitness" value="true" />
+ <option name="push" value="cintltst->/data/local/tmp/cintltst" />
<option name="post-push" value="chmod a+x /data/local/tmp/cintltst" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
- <option name="push" value="icu.intltest->/data/local/tmp/intltest" />
+ <option name="append-bitness" value="true" />
+ <option name="push" value="intltest->/data/local/tmp/intltest" />
<option name="post-push" value="chmod a+x /data/local/tmp/intltest" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
diff --git a/tests/tests/icu/icu4c_testapp/AndroidManifest.xml b/tests/tests/icu/icu4c_testapp/AndroidManifest.xml
index 8195600..c2051b4 100644
--- a/tests/tests/icu/icu4c_testapp/AndroidManifest.xml
+++ b/tests/tests/icu/icu4c_testapp/AndroidManifest.xml
@@ -16,7 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.icu4c.cts">
- <application>
+ <!-- extractNativeLibs="false" is needed to run on secondary ABI. -->
+ <application android:extractNativeLibs="false">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/tests/keystore/Android.bp b/tests/tests/keystore/Android.bp
index 0fa7cf7..71a4035 100644
--- a/tests/tests/keystore/Android.bp
+++ b/tests/tests/keystore/Android.bp
@@ -44,16 +44,16 @@
],
static_libs: [
"androidx.test.rules",
- "core-tests-support",
"compatibility-device-util-axt",
- "ctstestrunner-axt",
- "hamcrest-library",
- "guava",
- "junit",
- "cts-security-test-support-library",
+ "core-tests-support",
"cts-keystore-user-auth-helper-library",
- "platformprotosnano",
+ "cts-security-test-support-library",
"cts-wm-util",
+ "ctstestrunner-axt",
+ "guava",
+ "hamcrest-library",
+ "junit",
+ "platformprotosnano",
"testng",
],
srcs: ["src/android/keystore/**/*.java"],
diff --git a/tests/tests/keystore/src/android/keystore/cts/AttestKeyTest.java b/tests/tests/keystore/src/android/keystore/cts/AttestKeyTest.java
new file mode 100644
index 0000000..d5d12f3
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AttestKeyTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.keystore.cts.KeyAttestationTest.verifyCertificateChain;
+import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
+import static android.security.keystore.KeyProperties.PURPOSE_ATTEST_KEY;
+import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+import android.security.keystore.KeyGenParameterSpec;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import javax.security.auth.x500.X500Principal;
+
+public class AttestKeyTest {
+ private static final String TAG = AttestKeyTest.class.getSimpleName();
+
+ private KeyStore mKeyStore;
+ private ArrayList<String> mAliasesToDelete = new ArrayList();
+
+ @Before
+ public void setUp() throws Exception {
+ mKeyStore = KeyStore.getInstance("AndroidKeyStore");
+ mKeyStore.load(null);
+
+ // Assume attest key support for all tests in this class.
+ assumeAttestKey();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ for (String alias : mAliasesToDelete) {
+ try {
+ mKeyStore.deleteEntry(alias);
+ } catch (Throwable t) {
+ // Ignore any exception and delete the other aliases in the list.
+ }
+ }
+ }
+
+ @Test
+ public void testEcAttestKey() throws Exception {
+ final String attestKeyAlias = "attestKey";
+
+ Certificate attestKeyCertChain[] = generateKeyPair(KEY_ALGORITHM_EC,
+ new KeyGenParameterSpec.Builder(attestKeyAlias, PURPOSE_ATTEST_KEY)
+ .setAttestationChallenge("challenge".getBytes())
+ .build());
+ assertThat(attestKeyCertChain.length, greaterThan(1));
+
+ final String attestedKeyAlias = "attestedKey";
+ Certificate attestedKeyCertChain[] = generateKeyPair(KEY_ALGORITHM_EC,
+ new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
+ .setAttestationChallenge("challenge".getBytes())
+ .setAttestKeyAlias(attestKeyAlias)
+ .build());
+ // Even though we asked for an attestation, we only get one cert.
+ assertThat(attestedKeyCertChain.length, is(1));
+
+ verifyCombinedChain(attestKeyCertChain, attestedKeyCertChain);
+
+ X509Certificate attestationCert = (X509Certificate) attestedKeyCertChain[0];
+ Attestation attestation = Attestation.loadFromCertificate(attestationCert);
+ }
+
+ @Test
+ public void testAttestationWithNonAttestKey() throws Exception {
+ final String nonAttestKeyAlias = "nonAttestKey";
+ final String attestedKeyAlias = "attestedKey";
+ generateKeyPair(KEY_ALGORITHM_EC,
+ new KeyGenParameterSpec.Builder(nonAttestKeyAlias, PURPOSE_SIGN).build());
+
+ try {
+ generateKeyPair(KEY_ALGORITHM_EC,
+ new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
+ .setAttestationChallenge("challenge".getBytes())
+ .setAttestKeyAlias(nonAttestKeyAlias)
+ .build());
+ fail("Expected exception.");
+ } catch (InvalidAlgorithmParameterException e) {
+ assertThat(e.getMessage(), is("Invalid attestKey, does not have PURPOSE_ATTEST_KEY"));
+ }
+ }
+
+ @Test
+ public void testAttestKeyWithoutChallenge() throws Exception {
+ final String attestKeyAlias = "attestKey";
+ final String attestedKeyAlias = "attestedKey";
+ generateKeyPair(KEY_ALGORITHM_EC,
+ new KeyGenParameterSpec.Builder(attestKeyAlias, PURPOSE_ATTEST_KEY).build());
+
+ try {
+ generateKeyPair(KEY_ALGORITHM_EC,
+ new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
+ // Don't set attestation challenge
+ .setAttestKeyAlias(attestKeyAlias)
+ .build());
+ fail("Expected exception.");
+ } catch (InvalidAlgorithmParameterException e) {
+ assertThat(e.getMessage(),
+ is("AttestKey specified but no attestation challenge provided"));
+ }
+ }
+
+ @Test
+ public void testAttestKeySecurityLevelMismatch() throws Exception {
+ assumeStrongBox();
+
+ final String strongBoxAttestKeyAlias = "nonAttestKey";
+ final String attestedKeyAlias = "attestedKey";
+ generateKeyPair(KEY_ALGORITHM_EC,
+ new KeyGenParameterSpec.Builder(strongBoxAttestKeyAlias,
+ PURPOSE_ATTEST_KEY).setIsStrongBoxBacked(true).build());
+
+ try {
+ generateKeyPair(KEY_ALGORITHM_EC,
+ new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
+ .setAttestationChallenge("challenge".getBytes())
+ .setAttestKeyAlias(strongBoxAttestKeyAlias)
+ .build());
+ fail("Expected exception.");
+ } catch (InvalidAlgorithmParameterException e) {
+ assertThat(e.getMessage(),
+ is("Invalid security level: Cannot sign non-StrongBox key with StrongBox "
+ + "attestKey"));
+ }
+ }
+
+ private void assumeStrongBox() {
+ PackageManager packageManager =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ assumeTrue("Can only test if we have StrongBox",
+ packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE));
+ }
+
+ private void assumeAttestKey() {
+ PackageManager packageManager =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ assumeTrue("Can only test if we have the attest key feature.",
+ packageManager.hasSystemFeature(PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY));
+ }
+
+ private Certificate[] generateKeyPair(String algorithm, KeyGenParameterSpec spec)
+ throws NoSuchAlgorithmException, NoSuchProviderException,
+ InvalidAlgorithmParameterException, KeyStoreException {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm,
+ "AndroidKeyStore");
+ keyPairGenerator.initialize(spec);
+ keyPairGenerator.generateKeyPair();
+ mAliasesToDelete.add(spec.getKeystoreAlias());
+
+ return mKeyStore.getCertificateChain(spec.getKeystoreAlias());
+ }
+
+ private void verifyCombinedChain(Certificate[] attestKeyCertChain,
+ Certificate[] attestedKeyCertChain) throws GeneralSecurityException {
+ Certificate[] combinedChain = Stream
+ .concat(Arrays.stream(attestedKeyCertChain),
+ Arrays.stream(attestKeyCertChain))
+ .toArray(Certificate[]::new);
+
+ int i = 0;
+ for (Certificate cert : combinedChain) {
+ Log.e(TAG, "Certificate " + i + ": " + cert);
+ ++i;
+ }
+
+ verifyCertificateChain((Certificate[]) combinedChain, false /* expectStrongBox */);
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 456a7c1..a4a6267 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -46,6 +46,7 @@
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;
@@ -79,6 +80,8 @@
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;
@@ -1216,7 +1219,7 @@
keyPairGenerator.generateKeyPair();
}
- private void verifyCertificateChain(Certificate[] certChain, boolean expectStrongBox)
+ public static void verifyCertificateChain(Certificate[] certChain, boolean expectStrongBox)
throws GeneralSecurityException {
assertNotNull(certChain);
for (int i = 1; i < certChain.length; ++i) {
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java
index 3f1cbb6..b9f0977 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java
@@ -72,6 +72,7 @@
assertEquals(0, spec.getUserAuthenticationValidityDurationSeconds());
assertEquals(KeyProperties.AUTH_BIOMETRIC_STRONG, spec.getUserAuthenticationType());
assertFalse(spec.isUnlockedDeviceRequired());
+ assertEquals(KeyProperties.UNRESTRICTED_USAGE_COUNT, spec.getMaxUsageCount());
}
public void testSettersReflectedInGetters() {
@@ -83,6 +84,7 @@
Date keyValidityEndDateForOrigination = new Date(System.currentTimeMillis() + 11111111);
Date keyValidityEndDateForConsumption = new Date(System.currentTimeMillis() + 33333333);
AlgorithmParameterSpec algSpecificParams = new ECGenParameterSpec("secp256r1");
+ int maxUsageCount = 1;
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
"arbitrary", KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_ENCRYPT)
@@ -106,6 +108,7 @@
.setUserAuthenticationParameters(12345,
KeyProperties.AUTH_DEVICE_CREDENTIAL | KeyProperties.AUTH_BIOMETRIC_STRONG)
.setUnlockedDeviceRequired(true)
+ .setMaxUsageCount(maxUsageCount)
.build();
assertEquals("arbitrary", spec.getKeystoreAlias());
@@ -135,6 +138,7 @@
assertEquals(KeyProperties.AUTH_DEVICE_CREDENTIAL | KeyProperties.AUTH_BIOMETRIC_STRONG,
spec.getUserAuthenticationType());
assertTrue(spec.isUnlockedDeviceRequired());
+ assertEquals(maxUsageCount, spec.getMaxUsageCount());
}
public void testNullAliasNotPermitted() {
@@ -350,4 +354,12 @@
.build());
assertThrows(ProviderException.class, keyGenerator::generateKey);
}
+
+ public void testIllegalMaxUsageCountNotPermitted() {
+ try {
+ new KeyGenParameterSpec.Builder("LimitedUseKey", KeyProperties.PURPOSE_ENCRYPT)
+ .setMaxUsageCount(0);
+ fail();
+ } catch (IllegalArgumentException expected) {}
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
index 8a238d0..431ffa5 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
@@ -725,6 +725,30 @@
}
}
+ public void testLimitedUseKey() throws Exception {
+ testLimitedUseKey(false /* useStrongbox */);
+ if (TestUtils.hasStrongBox(getContext())) {
+ testLimitedUseKey(true /* useStrongbox */);
+ }
+ }
+
+ private void testLimitedUseKey(boolean useStrongbox) throws Exception {
+ int maxUsageCount = 1;
+ for (String algorithm :
+ (useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS)) {
+ try {
+ int expectedSizeBits = DEFAULT_KEY_SIZES.get(algorithm);
+ KeyGenerator keyGenerator = getKeyGenerator(algorithm);
+ keyGenerator.init(getWorkingSpec().setMaxUsageCount(maxUsageCount).build());
+ SecretKey key = keyGenerator.generateKey();
+ assertEquals(expectedSizeBits, TestUtils.getKeyInfo(key).getKeySize());
+ assertEquals(maxUsageCount, TestUtils.getKeyInfo(key).getRemainingUsageCount());
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed for " + algorithm, e);
+ }
+ }
+ }
+
private static KeyGenParameterSpec.Builder getWorkingSpec() {
return getWorkingSpec(0);
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java
index 2b1d6fc..d14f6d2 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java
@@ -26,6 +26,9 @@
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Security;
+import java.security.Signature;
import java.util.Arrays;
import java.util.Date;
@@ -93,5 +96,42 @@
String[] originalBlockModes = info.getBlockModes().clone();
info.getBlockModes()[0] = null;
assertEquals(Arrays.asList(originalBlockModes), Arrays.asList(info.getBlockModes()));
+
+ // Return KeyProperties.UNRESTRICTED_USAGE_COUNT to indicate there is no restriction on
+ // the number of times that the key can be used.
+ int remainingUsageCount = info.getRemainingUsageCount();
+ assertEquals(KeyProperties.UNRESTRICTED_USAGE_COUNT, remainingUsageCount);
+ }
+
+ public void testLimitedUseKey() throws Exception {
+ Date keyValidityStartDate = new Date(System.currentTimeMillis() - 2222222);
+ Date keyValidityEndDateForOrigination = new Date(System.currentTimeMillis() + 11111111);
+ Date keyValidityEndDateForConsumption = new Date(System.currentTimeMillis() + 33333333);
+ int maxUsageCount = 1;
+
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
+ keyPairGenerator.initialize(new KeyGenParameterSpec.Builder(
+ KeyInfoTest.class.getSimpleName(),
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_ENCRYPT)
+ .setKeySize(1024) // use smaller key size to speed the test up
+ .setKeyValidityStart(keyValidityStartDate)
+ .setKeyValidityForOriginationEnd(keyValidityEndDateForOrigination)
+ .setKeyValidityForConsumptionEnd(keyValidityEndDateForConsumption)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
+ KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
+ .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
+ KeyProperties.SIGNATURE_PADDING_RSA_PSS)
+ .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
+ .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
+ .setMaxUsageCount(maxUsageCount)
+ .build());
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+ PrivateKey key = keyPair.getPrivate();
+ KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore");
+ KeyInfo info = keyFactory.getKeySpec(key, KeyInfo.class);
+
+ int remainingUsageCount = info.getRemainingUsageCount();
+ assertEquals(maxUsageCount, remainingUsageCount);
}
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
index 274141e..07cf203 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
@@ -17,6 +17,7 @@
package android.keystore.cts;
import android.security.GateKeeper;
+import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.test.MoreAsserts;
@@ -51,6 +52,7 @@
assertEquals(KeyProperties.AUTH_BIOMETRIC_STRONG, spec.getUserAuthenticationType());
assertEquals(GateKeeper.INVALID_SECURE_USER_ID, spec.getBoundToSpecificSecureUserId());
assertFalse(spec.isUnlockedDeviceRequired());
+ assertEquals(KeyProperties.UNRESTRICTED_USAGE_COUNT, spec.getMaxUsageCount());
}
public void testSettersReflectedInGetters() {
@@ -59,6 +61,7 @@
Date keyValidityStartDate = new Date(System.currentTimeMillis() - 2222222);
Date keyValidityEndDateForOrigination = new Date(System.currentTimeMillis() + 11111111);
Date keyValidityEndDateForConsumption = new Date(System.currentTimeMillis() + 33333333);
+ int maxUsageCount = 1;
KeyProtection spec = new KeyProtection.Builder(
KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_VERIFY)
@@ -77,6 +80,7 @@
KeyProperties.AUTH_DEVICE_CREDENTIAL | KeyProperties.AUTH_BIOMETRIC_STRONG)
.setBoundToSpecificSecureUserId(654321)
.setUnlockedDeviceRequired(true)
+ .setMaxUsageCount(maxUsageCount)
.build();
assertEquals(
@@ -100,6 +104,7 @@
spec.getUserAuthenticationType());
assertEquals(654321, spec.getBoundToSpecificSecureUserId());
assertTrue(spec.isUnlockedDeviceRequired());
+ assertEquals(spec.getMaxUsageCount(), maxUsageCount);
}
public void testSetKeyValidityEndDateAppliesToBothEndDates() {
@@ -263,4 +268,11 @@
assertEquals(Arrays.asList(originalSignaturePaddings),
Arrays.asList(spec.getSignaturePaddings()));
}
+
+ public void testIllegalMaxUsageCountNotPermitted() {
+ try {
+ new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT).setMaxUsageCount(0);
+ fail();
+ } catch (IllegalArgumentException expected) {}
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
index 086e0b1..cbeb7d7 100644
--- a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
@@ -19,7 +19,10 @@
import android.keystore.cts.R;
import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactory;
import java.security.KeyPair;
+import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Provider.Service;
@@ -37,6 +40,8 @@
import java.util.TreeMap;
import android.content.Context;
+import android.security.keystore.KeyInfo;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.test.AndroidTestCase;
@@ -421,6 +426,62 @@
}
}
+ public void testValidSignatureGeneratedForEmptyMessageByLimitedUseKey()
+ throws Exception {
+ Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+ assertNotNull(provider);
+ int maxUsageCount = 2;
+ for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+ for (ImportedKey key : importLimitedUseKatKeyPairsForSigning(getContext(), sigAlgorithm, maxUsageCount)) {
+ if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(
+ sigAlgorithm, key.getOriginalSigningKey())) {
+ continue;
+ }
+ try {
+ KeyPair keyPair = key.getKeystoreBackedKeyPair();
+ KeyFactory keyFactory = KeyFactory.getInstance(
+ keyPair.getPrivate().getAlgorithm(), "AndroidKeyStore");
+ KeyInfo info = keyFactory.getKeySpec(keyPair.getPrivate(), KeyInfo.class);
+ assertEquals(maxUsageCount, info.getRemainingUsageCount());
+
+ // The first usage of the limited use key.
+ // Generate a signature
+ Signature signature = Signature.getInstance(sigAlgorithm, provider);
+ signature.initSign(keyPair.getPrivate());
+ byte[] sigBytes = signature.sign();
+ // Assert that it verifies using our own Provider
+ signature.initVerify(keyPair.getPublic());
+ assertTrue(signature.verify(sigBytes));
+
+ // After using the key, the remaining usage count should be decreased. But this
+ // will not be reflected unless we loads the key entry again.
+ info = keyFactory.getKeySpec(keyPair.getPrivate(), KeyInfo.class);
+ assertEquals(maxUsageCount, info.getRemainingUsageCount());
+
+ // Reloads the key entry again, the remaining usage count is decreased.
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ KeyStore.PrivateKeyEntry entry =
+ (KeyStore.PrivateKeyEntry) keyStore.getEntry(key.getAlias(), null);
+ info = keyFactory.getKeySpec(entry.getPrivateKey(), KeyInfo.class);
+ assertEquals(maxUsageCount - 1, info.getRemainingUsageCount());
+
+ // The second usage of the limited use key.
+ signature.initSign(keyPair.getPrivate());
+ signature.sign();
+
+ // After the usage of limited use key is exhausted, the key will be deleted.
+ try {
+ signature.initSign(keyPair.getPrivate());
+ } catch (KeyPermanentlyInvalidatedException expected) {}
+ } catch (Throwable e) {
+ throw new RuntimeException(
+ "Failed for " + sigAlgorithm + " with key " + key.getAlias(), e);
+ }
+ }
+ }
+ }
+
public void testEmptySignatureDoesNotVerify()
throws Exception {
Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -1259,4 +1320,19 @@
throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
}
+
+ static Collection<ImportedKey> importLimitedUseKatKeyPairsForSigning(
+ Context context, String signatureAlgorithm, int maxUsageCount) throws Exception {
+ String keyAlgorithm = TestUtils.getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
+ KeyProtection importParams =
+ TestUtils.getMinimalWorkingImportParametersWithLimitedUsageForSigningingWith(
+ signatureAlgorithm, maxUsageCount);
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ return ECDSASignatureTest.importKatKeyPairs(context, importParams);
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ return RSASignatureTest.importKatKeyPairs(context, importParams);
+ } else {
+ throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+ }
+ }
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
index dcfcc85..0392ad5 100644
--- a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
+++ b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
@@ -932,6 +932,28 @@
}
}
+ static KeyProtection getMinimalWorkingImportParametersWithLimitedUsageForSigningingWith(
+ String signatureAlgorithm, int maxUsageCount) {
+ String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
+ String digest = getSignatureAlgorithmDigest(signatureAlgorithm);
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+ .setDigests(digest)
+ .setMaxUsageCount(maxUsageCount)
+ .build();
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ String padding = getSignatureAlgorithmPadding(signatureAlgorithm);
+ return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+ .setDigests(digest)
+ .setSignaturePaddings(padding)
+ .setMaxUsageCount(maxUsageCount)
+ .build();
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported signature algorithm: " + signatureAlgorithm);
+ }
+ }
+
static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
String transformation, int purposes) {
return getMinimalWorkingImportParametersForCipheringWith(transformation, purposes, false);
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 8824360..77ab2a4 100755
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -2918,6 +2918,22 @@
audioTrack.getAudioDescriptionMixLeveldB(), 0.f /*delta*/);
}
+ @Test
+ public void testSetLogSessionId() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+ AudioTrack audioTrack = null;
+ try {
+ audioTrack = new AudioTrack.Builder().build();
+ audioTrack.setLogSessionId("Hello World"); // for now, any string will do.
+ } finally {
+ if (audioTrack != null) {
+ audioTrack.release();
+ }
+ }
+ }
+
/* Do not run in JB-MR1. will be re-opened in the next platform release.
public void testResourceLeakage() throws Exception {
final int BUFFER_SIZE = 600 * 1024;
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
index e3c7f6b..9945d8c 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
@@ -21,6 +21,9 @@
import android.media.MediaDrm.MediaDrmStateException;
import android.media.MediaDrmException;
import android.media.MediaFormat;
+import android.media.NotProvisionedException;
+import android.media.ResourceBusyException;
+import android.media.UnsupportedSchemeException;
import android.media.cts.TestUtils.Monitor;
import android.net.Uri;
import android.os.Looper;
@@ -1167,8 +1170,9 @@
byte[] ignoredInitData = new byte[] { 1 };
drm.getKeyRequest(sessionId, ignoredInitData, "cenc", MediaDrm.KEY_TYPE_STREAMING, null);
} catch (MediaDrm.SessionException e) {
- if (e.getErrorCode() != MediaDrm.SessionException.ERROR_RESOURCE_CONTENTION) {
- throw new Error("Incorrect error code, expected ERROR_RESOURCE_CONTENTION");
+ if (e.getErrorCode() != MediaDrm.SessionException.ERROR_RESOURCE_CONTENTION ||
+ !e.isTransient()) {
+ throw new Error("Expected transient ERROR_RESOURCE_CONTENTION");
}
gotException = true;
}
@@ -1250,6 +1254,35 @@
}
}
+ @Presubmit
+ public void testMediaDrmStateExceptionErrorCode()
+ throws ResourceBusyException, UnsupportedSchemeException, NotProvisionedException {
+ if (watchHasNoClearkeySupport()) {
+ return;
+ }
+
+ MediaDrm drm = null;
+ try {
+ drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+ byte[] sessionId = drm.openSession();
+ drm.closeSession(sessionId);
+
+ byte[] ignoredInitData = new byte[]{1};
+ drm.getKeyRequest(sessionId, ignoredInitData, "cenc",
+ MediaDrm.KEY_TYPE_STREAMING,
+ null);
+ } catch(MediaDrmStateException e) {
+ Log.i(TAG, "Verifying exception error code", e);
+ assertFalse("ERROR_SESSION_NOT_OPENED requires new session", e.isTransient());
+ assertEquals("Expected ERROR_SESSION_NOT_OPENED",
+ MediaDrm.ErrorCodes.ERROR_SESSION_NOT_OPENED, e.getErrorCode());
+ } finally {
+ if (drm != null) {
+ drm.close();
+ }
+ }
+ }
+
private String getClearkeyVersion(MediaDrm drm) {
try {
return drm.getPropertyString("version");
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmTest.java b/tests/tests/media/src/android/media/cts/MediaDrmTest.java
index adc927e..b4fd272 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmTest.java
@@ -17,10 +17,14 @@
package android.media.cts;
import android.media.MediaDrm;
+import android.media.NotProvisionedException;
import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
+
import java.util.List;
import java.util.UUID;
+
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,4 +59,36 @@
}
}
+ @Test
+ public void testGetLogMessages() throws Exception {
+ List<UUID> supportedCryptoSchemes = MediaDrm.getSupportedCryptoSchemes();
+ for (UUID scheme : supportedCryptoSchemes) {
+ MediaDrm drm = new MediaDrm(scheme);
+ try {
+ byte[] sid = drm.openSession();
+ drm.closeSession(sid);
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, scheme.toString() + ": not provisioned", e);
+ }
+
+ List<MediaDrm.LogMessage> logMessages;
+ try {
+ logMessages = drm.getLogMessages();
+ Assert.assertFalse("Empty logs", logMessages.isEmpty());
+ } catch (UnsupportedOperationException e) {
+ Log.w(TAG, scheme.toString() + ": no LogMessage support", e);
+ continue;
+ }
+
+ long end = System.currentTimeMillis();
+ for (MediaDrm.LogMessage log: logMessages) {
+ Assert.assertTrue("Log occurred in future",
+ log.timestampMillis <= end);
+ Assert.assertTrue("Invalid log priority",
+ log.priority >= Log.VERBOSE &&
+ log.priority <= Log.ASSERT);
+ Log.i(TAG, log.toString());
+ }
+ }
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
index 2a0e84f..6fd33e8 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
@@ -36,6 +36,7 @@
import android.os.Process;
import android.test.InstrumentationTestCase;
import android.test.UiThreadTest;
+import android.util.Log;
import android.view.KeyEvent;
import java.io.IOException;
@@ -399,6 +400,24 @@
}
}
+ public void testCustomClassConfigValuesAreValid() throws Exception {
+ final Context context = getInstrumentation().getTargetContext();
+ String customMediaKeyDispatcher = context.getString(
+ android.R.string.config_customMediaKeyDispatcher);
+ String customMediaSessionPolicyProvider = context.getString(
+ android.R.string.config_customMediaSessionPolicyProvider);
+ // MediaSessionService will call Class.forName(String) with the existing config value.
+ // If the config value is not valid (i.e. given class doesn't exist), the following
+ // methods will return false.
+ if (!customMediaKeyDispatcher.isEmpty()) {
+ assertTrue(mSessionManager.hasCustomMediaKeyDispatcher(customMediaKeyDispatcher));
+ }
+ if (!customMediaSessionPolicyProvider.isEmpty()) {
+ assertTrue(mSessionManager.hasCustomMediaSessionPolicyProvider(
+ customMediaSessionPolicyProvider));
+ }
+ }
+
private boolean listContainsToken(List<Session2Token> tokens, Session2Token token) {
for (int i = 0; i < tokens.size(); i++) {
if (tokens.get(i).equals(token)) {
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
index 409812d..6e31c44 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
@@ -133,9 +133,11 @@
public void setUp() throws Exception {
Log.d(TAG, "setUp");
super.setUp();
-
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mContentResolver = mContext.getContentResolver();
+
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity("android.permission.WRITE_MEDIA_STORAGE");
mMediaTranscodeManager = mContext.getSystemService(MediaTranscodeManager.class);
assertNotNull(mMediaTranscodeManager);
androidx.test.InstrumentationRegistry.registerInstance(
@@ -155,6 +157,8 @@
@Override
public void tearDown() throws Exception {
+ InstrumentationRegistry
+ .getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
super.tearDown();
}
@@ -614,7 +618,8 @@
// Transcoding video on behalf of init dameon and expect UnsupportedOperationException due to
// CTS test is not a privilege caller.
- public void testPidAndUidForwarding() throws Exception {
+ // Disable this test as Android S will only allow MediaProvider to access the API.
+ /*public void testPidAndUidForwarding() throws Exception {
if (shouldSkip()) {
return;
}
@@ -644,7 +649,7 @@
transcodeCompleteSemaphore.release();
});
});
- }
+ }*/
public void testTranscodingProgressUpdate() throws Exception {
if (shouldSkip()) {
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
index 8b50b27..bc9555c 100644
--- a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
+++ b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
@@ -60,6 +60,14 @@
boolean expected = getBooleanProperty(mInstrumentation,
"ro.fw.mu.headless_system_user");
assertWithMessage("isHeadlessSystemUserMode()")
- .that(mUserManager.isHeadlessSystemUserMode()).isEqualTo(expected);
+ .that(UserManager.isHeadlessSystemUserMode()).isEqualTo(expected);
}
+
+ @Test
+ public void testIsUserForeground_currentUser() throws Exception {
+ assertWithMessage("isUserForeground() for current user")
+ .that(mUserManager.isUserForeground()).isTrue();
+ }
+ // TODO(b/173541467): add testIsUserForeground_backgroundUser()
+ // TODO(b/179163496): add testIsUserForeground_ tests for profile users
}
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 edf68de..e46b709 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,8 @@
package android.app.notification.legacy29.cts;
+import static android.service.notification.NotificationAssistantService.FEEDBACK_RATING;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertFalse;
@@ -37,7 +39,6 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
-import android.os.UserHandle;
import android.provider.Telephony;
import android.service.notification.Adjustment;
import android.service.notification.NotificationAssistantService;
@@ -55,12 +56,8 @@
import org.junit.runner.RunWith;
import java.io.BufferedReader;
-import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -671,6 +668,23 @@
}
+ @Test
+ public void testOnNotificationFeedbackReceived() throws Exception {
+ setUpListeners(); // also enables assistant
+ mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", "android.permission.EXPAND_STATUS_BAR");
+
+ sendNotification(1, ICON_ID);
+ StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
+
+ Bundle feedback = new Bundle();
+ feedback.putInt(FEEDBACK_RATING, 1);
+
+ mStatusBarManager.sendNotificationFeedback(sbn.getKey(), feedback);
+ Thread.sleep(SLEEP_TIME * 2);
+ assertEquals(1, mNotificationAssistantService.notificationFeedback);
+
+ mUi.dropShellPermissionIdentity();
+ }
private StatusBarNotification getFirstNotificationFromPackage(String PKG)
throws InterruptedException {
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
index 684bb02..e8a854b 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
@@ -39,6 +39,7 @@
int notificationHiddenCount = 0;
int notificationClickCount = 0;
int notificationRank = -1;
+ int notificationFeedback = 0;
String snoozedKey;
String snoozedUntilContext;
private NotificationManager mNotificationManager;
@@ -140,4 +141,10 @@
@Override
public void onNotificationClicked(String key) { notificationClickCount++; }
+
+ @Override
+ public void onNotificationFeedbackReceived(String key, RankingMap rankingMap, Bundle feedback) {
+ notificationFeedback = feedback.getInt(FEEDBACK_RATING, 0);
+ }
+
}
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
index 23ff440..b786cf5 100644
--- a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -136,7 +136,6 @@
@Test
fun testProfiles() {
val packageName = "android.os.cts.companiontestapp"
- runShellCommand("pm uninstall $packageName")
installApk("/data/local/tmp/cts/os/CtsCompanionTestApp.apk")
startApp(packageName)
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
index 3ce0de3..08946e8 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
@@ -23,5 +23,6 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
index b1ac0da..8487923 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
@@ -23,5 +23,6 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
index f54f561..dd6d36a 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
@@ -23,5 +23,6 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
index f76f466..8aed828 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
@@ -23,5 +23,6 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/AppThatDefinesPermissionA/Android.bp b/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
index 406738c..29f8d52 100644
--- a/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
@@ -23,5 +23,6 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
index 4d52d80..827e340 100644
--- a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
@@ -22,5 +22,6 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
index c2cd7d8..7e0bc5d 100644
--- a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
@@ -22,5 +22,6 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
index 104fad9..1889d38 100644
--- a/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
@@ -22,5 +22,6 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/OWNERS b/tests/tests/permission/OWNERS
index 472beea..25bf9ca 100644
--- a/tests/tests/permission/OWNERS
+++ b/tests/tests/permission/OWNERS
@@ -6,4 +6,4 @@
per-file Camera2PermissionTest.java = file: platform/frameworks/av:/camera/OWNERS
per-file OneTimePermissionTest.java, AppThatRequestOneTimePermission/... = evanseverson@google.com
per-file LocationAccessCheckTest.java = ntmyren@google.com
-per-file NoRollbackPermissionTest.java = mpgroover@google.com
+per-file NoRollbackPermissionTest.java = mpgroover@google.com
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java b/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
index ddf8b19..b842cc0 100644
--- a/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
@@ -34,16 +34,6 @@
}
}
- public void testGetPowerSaverMode_requiresPermissions() {
- try {
- PowerManager manager = getContext().getSystemService(PowerManager.class);
- manager.getPowerSaveModeTrigger();
- fail("Getting the current power saver mode requires the POWER_SAVER permission");
- } catch (SecurityException e) {
- // Expected Exception
- }
- }
-
public void testSetDynamicPowerSavings_requiresPermissions() {
try {
PowerManager manager = getContext().getSystemService(PowerManager.class);
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index a4fb6d8..2f1215c 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1309,6 +1309,8 @@
<!-- @hide Allows an application to Access UCE-Presence.
<p>Protection level: signature|privileged
+ @deprecated Framework should no longer use this permission to access the vendor UCE service
+ using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
-->
<permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE"
android:permissionGroup="android.permission-group.PHONE"
@@ -1316,6 +1318,8 @@
<!-- @hide Allows an application to Access UCE-OPTIONS.
<p>Protection level: signature|privileged
+ @deprecated Framework should no longer use this permission to access the vendor UCE service
+ using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
-->
<permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE"
android:permissionGroup="android.permission-group.PHONE"
@@ -2001,6 +2005,12 @@
<permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows access to ultra wideband device.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.UWB_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
<!-- ================================== -->
<!-- Permissions for accessing accounts -->
<!-- ================================== -->
@@ -2262,6 +2272,11 @@
<permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows read access to privileged network state in the device config.
+ @hide Used internally. -->
+ <permission android:name="android.permission.READ_NETWORK_DEVICE_CONFIG"
+ android:protectionLevel="signature|privileged" />
+
<!-- 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
of the subscriber.
@@ -2445,6 +2460,15 @@
<permission android:name="android.permission.BIND_GBA_SERVICE"
android:protectionLevel="signature" />
+ <!-- Required for an Application to access APIs related to RCS User Capability Exchange.
+ <p> This permission is only granted to system applications fulfilling the SMS, Dialer, and
+ Contacts app roles.
+ <p>Protection level: internal|role
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
+ android:protectionLevel="internal|role" />
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->
@@ -5447,17 +5471,23 @@
<permission android:name="android.permission.INPUT_CONSUMER"
android:protectionLevel="signature" />
+ <!-- Allows an app to use exact alarm scheduling APIs to perform timing
+ sensitive background work.
+ -->
+ <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+ android:protectionLevel="normal|appop"/>
+
<!-- @hide Allows an application to control the system's device state managed by the
{@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
devices this would grant access to toggle between the folded and unfolded states. -->
<permission android:name="android.permission.CONTROL_DEVICE_STATE"
android:protectionLevel="signature" />
- <!-- Must be required by an {@link android.service.screenshot.ScreenshotHasherService}
+ <!-- Must be required by an {@link android.service.displayhash.DisplayHasherService}
to ensure that only the system can bind to it.
@hide This is not a third-party API (intended for OEMs and system apps).
-->
- <permission android:name="android.permission.BIND_SCREENSHOT_HASHER_SERVICE"
+ <permission android:name="android.permission.BIND_DISPLAY_HASHER_SERVICE"
android:protectionLevel="signature" />
<!-- @hide @TestApi Allows an application to enable/disable toast rate limiting.
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
index 8a4ed43..5d0ca66 100644
--- a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -117,6 +117,9 @@
)
}
+ pressBack()
+ pressBack()
+ pressHome()
pressHome()
}
@@ -153,8 +156,6 @@
uiDevice.openNotification()
assertPrivacyChipAndIndicatorsPresent(useMic, useCamera)
}
-
- pressBack()
}
private fun assertTvIndicatorsShown(useMic: Boolean, useCamera: Boolean) {
diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp
index 677222d..ab52779 100644
--- a/tests/tests/security/Android.bp
+++ b/tests/tests/security/Android.bp
@@ -54,6 +54,7 @@
"src/**/*.java",
"src/android/security/cts/activity/ISecureRandomService.aidl",
"aidl/android/security/cts/IIsolatedService.aidl",
+ "aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl",
],
//sdk_version: "current",
platform_apis: true,
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 76fc9cf..45f69c1 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -119,6 +119,58 @@
</intent-filter>
</activity>
+ <activity android:name="android.security.cts.CVE_2021_0327.IntroActivity"
+ 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.security.cts.CVE_2021_0327.OtherUserActivity"
+ 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.security.cts.CVE_2021_0327.TestActivity"
+ 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.security.cts.CVE_2021_0327.workprofilesetup.ProvisionedActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.app.action.PROVISIONING_SUCCESSFUL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <receiver
+ android:name="android.security.cts.CVE_2021_0327.workprofilesetup.AdminReceiver"
+ android:permission="android.permission.BIND_DEVICE_ADMIN"
+ android:exported="true">
+ <meta-data
+ android:name="android.app.device_admin"
+ android:resource="@xml/device_admin" />
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+ </intent-filter>
+ </receiver>
+
+ <provider
+ android:name="android.security.cts.CVE_2021_0327.BadProvider"
+ android:authorities="android.security.cts.CVE_2021_0327.BadProvider"
+ android:exported="false"
+ android:grantUriPermissions="true"
+ android:process=":badprovider" />
+
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml
index a7d1ede..6e0c8bc4 100644
--- a/tests/tests/security/AndroidTest.xml
+++ b/tests/tests/security/AndroidTest.xml
@@ -17,7 +17,8 @@
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <!-- CtsDeviceInfo target API is 23; instant app requires target API >= 26. -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<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" />
@@ -33,6 +34,16 @@
value="appops reset android.security.cts" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="teardown-command"
+ value="pm remove-user 10" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="teardown-command"
+ value="pm uninstall --user 0 android.security.cts" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.security.cts" />
<option name="runtime-hint" value="1h40m18s" />
diff --git a/tests/tests/security/aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl b/tests/tests/security/aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl
new file mode 100644
index 0000000..e71f2c8
--- /dev/null
+++ b/tests/tests/security/aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl
@@ -0,0 +1,10 @@
+// IBadProvider.aidl
+package android.security.cts.CVE_2021_0327;
+
+// Declare any non-default types here with import statements
+import android.os.ParcelFileDescriptor;
+interface IBadProvider {
+ ParcelFileDescriptor takeBinder();
+
+ oneway void exit();
+}
diff --git a/tests/tests/security/res/xml/device_admin.xml b/tests/tests/security/res/xml/device_admin.xml
new file mode 100644
index 0000000..5760898
--- /dev/null
+++ b/tests/tests/security/res/xml/device_admin.xml
@@ -0,0 +1,14 @@
+<?xml version ="1.0" encoding ="utf-8"?>
+<device-admin>
+ <uses-policies>
+<!-- <limit-password/>-->
+<!-- <watch-login/>-->
+<!-- <reset-password/>-->
+<!-- <force-lock/>-->
+<!-- <wipe-data/>-->
+<!-- <expire-password/>-->
+<!-- <encrypted-storage/>-->
+<!-- <disable-camera/>-->
+<!-- <disable-keyguard-features/>-->
+ </uses-policies>
+</device-admin>
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/BadProvider.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/BadProvider.java
new file mode 100644
index 0000000..d0b6cad
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/BadProvider.java
@@ -0,0 +1,76 @@
+package android.security.cts.CVE_2021_0327;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.IOException;
+
+public class BadProvider extends ContentProvider {
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ if ("get_aidl".equals(method)) {
+ Bundle bundle = new Bundle();
+ bundle.putBinder("a", new IBadProvider.Stub() {
+ @Override
+ public ParcelFileDescriptor takeBinder() throws RemoteException {
+ for (int i = 0; i < 100; i++) {
+ try {
+ String name = Os.readlink("/proc/" + Process.myPid() + "/fd/" + i);
+ // Log.v("TAKEBINDER", "fd=" + i + " path=" + name);
+ if (name.startsWith("/dev/") && name.endsWith("/binder")) {
+ return ParcelFileDescriptor.fromFd(i);
+ }
+ } catch (ErrnoException | IOException e) {
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void exit() throws RemoteException {
+ System.exit(0);
+ }
+ });
+ return bundle;
+ }
+ return super.call(method, arg, extras);
+ }
+
+ @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/security/src/android/security/cts/CVE_2021_0327/CVE_2021_0327.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/CVE_2021_0327.java
new file mode 100644
index 0000000..56408ee
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/CVE_2021_0327.java
@@ -0,0 +1,81 @@
+/*
+ * 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.security.cts.CVE_2021_0327;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.platform.test.annotations.SecurityTest;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.InstrumentationRegistry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@SecurityTest
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_0327 {
+
+ private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
+ private static final String TAG = "CVE_2021_0327";
+ public static boolean testActivityRequested;
+ public static boolean testActivityCreated;
+ public static String errorMessage = null;
+
+ private void launchActivity(Class<? extends Activity> clazz) {
+ final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(SECURITY_CTS_PACKAGE_NAME, clazz.getName());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+
+ /**
+ * b/175817081
+ */
+ @Test
+ @SecurityTest
+ public void testPocCVE_2021_0327() throws Exception {
+ Log.d(TAG, "test start");
+ testActivityCreated=false;
+ testActivityRequested=false;
+ launchActivity(IntroActivity.class);
+ synchronized(CVE_2021_0327.class){
+ if(!testActivityRequested) {
+ Log.d(TAG, "test is waiting for TestActivity to be requested to run");
+ CVE_2021_0327.class.wait();
+ Log.d(TAG, "got signal");
+ }
+ }
+ synchronized(CVE_2021_0327.class){
+ if(!testActivityCreated) {
+ Log.d(TAG, "test is waiting for TestActivity to run");
+ CVE_2021_0327.class.wait(10000);
+ Log.d(TAG, "got signal");
+ }
+ }
+ Log.d(TAG, "test completed. testActivityCreated=" + testActivityCreated);
+ if(errorMessage != null){
+ Log.d(TAG, "errorMessage=" + errorMessage);
+ }
+ assertTrue(errorMessage==null);
+ assertFalse(testActivityCreated);
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java
new file mode 100644
index 0000000..fd2af3a
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java
@@ -0,0 +1,129 @@
+package android.security.cts.CVE_2021_0327;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.util.Log;
+import android.os.SystemClock;
+
+//import android.support.test.InstrumentationRegistry;
+import androidx.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import java.io.*;
+import java.util.stream.Collectors;
+
+import android.security.cts.CVE_2021_0327.workprofilesetup.AdminReceiver;
+
+public class IntroActivity extends Activity {
+
+ private static final int AR_WORK_PROFILE_SETUP = 1;
+ private static final String TAG = "CVE_2021_0327";
+
+ private void launchOtherUserActivity() {
+ Log.d(TAG,"launchOtherUserActivity()");
+ Intent intent = new Intent(this, OtherUserActivity.class);
+ intent.setClipData(new ClipData("d", new String[]{"a/b"}, new ClipData.Item(Uri.parse("content://android.security.cts.CVE_2021_0327.BadProvider"))));
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ startActivity(intent);
+ finish();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(TAG,"IntroActivity.OnCreate()");
+ if (isProfileOwner()) {
+ } else if (canLaunchOtherUserActivity()) {
+ launchOtherUserActivity();
+ } else {
+ setupWorkProfile(null);
+
+ //detect buttons to click
+ boolean profileSetUp=false;
+ String button;
+ java.util.List<UiObject2> objects;
+ UiDevice mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ BySelector selector = By.clickable(true);
+
+
+ while(!profileSetUp){
+ do {
+ Log.i(TAG, "waiting for clickable");
+ SystemClock.sleep(3000);
+ } while((objects = mUiDevice.findObjects(selector)).size()==0);
+ for(UiObject2 o : objects){
+ button=o.getText();
+ Log.d(TAG,"button:" + button);
+
+ if(button==null){
+ continue;
+ }
+
+ switch(button){
+ case "Delete" :
+ o.click();
+ Log.i(TAG, "clicked: Delete");
+ break;
+ case "Accept & continue" :
+ o.click();
+ Log.i(TAG, "clicked: Accept & continue");
+ break;
+ case "Next" :
+ o.click();
+ profileSetUp=true;
+ Log.i(TAG, "clicked: Next");
+ break;
+ default :
+ continue;
+ }
+ break;
+ }
+ }
+ //end while(!profileSetUp);
+ }
+ }
+
+ private boolean isProfileOwner() {
+ return getSystemService(DevicePolicyManager.class).isProfileOwnerApp(getPackageName());
+ }
+
+ private boolean canLaunchOtherUserActivity() {
+ Intent intent = new Intent("android.security.cts.CVE_2021_0327.OTHER_USER_ACTIVITY");
+ return (getPackageManager().resolveActivity(intent, 0) != null);
+ }
+
+ public void setupWorkProfile(View view) {
+ Log.d(TAG, "setupWorkProfile()");
+ Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
+ intent.putExtra(
+ DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
+ new ComponentName(this, AdminReceiver.class)
+ );
+ startActivityForResult(intent, AR_WORK_PROFILE_SETUP);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Log.d(TAG, "onActivityResult()");
+ if (requestCode == AR_WORK_PROFILE_SETUP) {
+ if (resultCode == RESULT_OK) {
+ launchOtherUserActivity();
+ } else {
+ new AlertDialog.Builder(this)
+ .setMessage("Work profile setup failed")
+ .setPositiveButton("ok", null)
+ .show();
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/OtherUserActivity.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/OtherUserActivity.java
new file mode 100644
index 0000000..3ed38de
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/OtherUserActivity.java
@@ -0,0 +1,94 @@
+package android.security.cts.CVE_2021_0327;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+import android.view.View;
+import android.util.Log;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.*;
+import java.util.stream.Collectors;
+import java.util.concurrent.TimeUnit;
+import android.os.SystemClock;
+
+public class OtherUserActivity extends Activity {
+
+ static ParcelFileDescriptor sTakenBinder;
+ private static final String TAG = "CVE_2021_0327";
+ String errorMessage = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ doMakeProviderBad(null);
+ SystemClock.sleep(5000);
+ doStuff(null);
+ }
+
+ public void doMakeProviderBad(View view) {
+ new MakeProviderBad().execute();
+ }
+
+ private class MakeProviderBad extends AsyncTask<Void, Void, Exception> {
+
+ @Override
+ protected Exception doInBackground(Void... voids) {
+ try {
+ if (sTakenBinder != null) {
+ sTakenBinder.close();
+ sTakenBinder = null;
+ }
+
+ Uri uri = getIntent().getClipData().getItemAt(0).getUri();
+ Bundle callResult = getContentResolver().call(uri, "get_aidl", null, null);
+ IBadProvider iface = IBadProvider.Stub.asInterface(callResult.getBinder("a"));
+ sTakenBinder = iface.takeBinder();
+ if (sTakenBinder == null) {
+ throw new Exception("Failed to find binder of provider");
+ }
+ iface.exit();
+ } catch (Exception e) {
+ errorMessage = errorMessage==null ? e.toString() : errorMessage + ", " + e.toString();
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Exception e) {
+ }
+ }
+
+ public void doStuff(View view) {
+ sendCommand("start", "--user", "0", "-d", "content://android.security.cts.CVE_2021_0327.BadProvider", "-n", "android.security.cts/.CVE_2021_0327.TestActivity");
+ }
+
+ @SuppressLint("PrivateApi")
+ void sendCommand(String... args) {
+ String[] command = new String[args.length + 2];
+ command[0] = "/system/bin/cmd";
+ command[1] = "activity";
+ System.arraycopy(args, 0, command, 2, args.length);
+
+ try{
+ Runtime.getRuntime().exec(command);
+ } catch(Exception e){
+ errorMessage = errorMessage==null ? e.toString() : errorMessage + ", " + e.toString();
+ }
+ finally{
+ synchronized(CVE_2021_0327.class){
+ CVE_2021_0327.testActivityRequested=true;
+ CVE_2021_0327.errorMessage = errorMessage;
+ CVE_2021_0327.class.notifyAll();
+ }
+ }
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/TestActivity.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/TestActivity.java
new file mode 100644
index 0000000..d44a220
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/TestActivity.java
@@ -0,0 +1,17 @@
+package android.security.cts.CVE_2021_0327;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d("CVE_2021_0327","TestActivity.onCreate()");
+ synchronized(CVE_2021_0327.class){
+ CVE_2021_0327.testActivityCreated=true;
+ CVE_2021_0327.class.notifyAll();
+ }
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/AdminReceiver.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/AdminReceiver.java
new file mode 100644
index 0000000..d374dfd
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/AdminReceiver.java
@@ -0,0 +1,7 @@
+package android.security.cts.CVE_2021_0327.workprofilesetup;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class AdminReceiver extends DeviceAdminReceiver {
+
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/ProvisionedActivity.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/ProvisionedActivity.java
new file mode 100644
index 0000000..1ae1c5d
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/workprofilesetup/ProvisionedActivity.java
@@ -0,0 +1,43 @@
+package android.security.cts.CVE_2021_0327.workprofilesetup;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+import android.security.cts.CVE_2021_0327.OtherUserActivity;
+
+public class ProvisionedActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getPackageManager().setComponentEnabledSetting(
+ new ComponentName(
+ this,
+ OtherUserActivity.class
+ ),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP
+ );
+
+ DevicePolicyManager manager = getSystemService(DevicePolicyManager.class);
+ //IntentFilter intentFilter = new IntentFilter("com.example.clearedshell.OTHER_USER_ACTIVITY");
+ IntentFilter intentFilter = new IntentFilter("android.security.cts.CVE_2021_0327.OTHER_USER_ACTIVITY");
+ //IntentFilter intentFilter = new IntentFilter("android.security.cts.CVE_2021_0327$OTHER_USER_ACTIVITY");
+ intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+ ComponentName admin = new ComponentName(this, AdminReceiver.class);
+ manager.addCrossProfileIntentFilter(
+ admin,
+ intentFilter,
+ DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT
+ );
+ manager.setProfileEnabled(admin);
+
+ setResult(RESULT_OK);
+ finish();
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0339.java b/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
index 5ace5df..a59d749 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
@@ -17,98 +17,126 @@
package android.security.cts;
import static org.junit.Assert.assertThat;
-import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assume.assumeThat;
+import static org.hamcrest.Matchers.*;
-import android.test.AndroidTestCase;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
-import android.platform.test.annotations.SecurityTest;
+import android.os.Bundle;
+import android.os.RemoteCallback;
import android.os.SystemClock;
+import android.platform.test.annotations.SecurityTest;
+import android.test.AndroidTestCase;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import android.util.Log;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
@SecurityTest
@RunWith(AndroidJUnit4.class)
public class CVE_2021_0339 {
static final String TAG = CVE_2021_0339.class.getSimpleName();
private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
+ private static final String CALLBACK_KEY = "testactivitycallback";
+ private static final String RESULT_KEY = "testactivityresult";
+ static final int DURATION_RESULT_CODE = 1;
static final int MAX_TRANSITION_DURATION_MS = 3000; // internal max
static final int TIME_MEASUREMENT_DELAY_MS = 5000; // tolerance for lag.
- public static boolean testCompleted;
- public FirstActivity fActivity;
- public SecondActivity sActivity;
-
- private void launchActivity(Class<? extends Activity> clazz) {
+ private void launchActivity(Class<? extends Activity> clazz, RemoteCallback cb) {
final Context context = InstrumentationRegistry.getInstrumentation().getContext();
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(SECURITY_CTS_PACKAGE_NAME, clazz.getName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(CALLBACK_KEY, cb);
context.startActivity(intent);
}
/**
* b/175817167
+ * start the first activity and get the result from the remote callback
*/
@Test
@SecurityTest
public void testPocCVE_2021_0339() throws Exception {
+ CompletableFuture<Integer> callbackReturn = new CompletableFuture<>();
+ RemoteCallback cb = new RemoteCallback((Bundle result) ->
+ callbackReturn.complete(result.getInt(RESULT_KEY)));
+ launchActivity(FirstActivity.class, cb); // start activity with callback as intent extra
- Log.d(TAG, "test start");
- testCompleted = false;
- launchActivity(FirstActivity.class);
+ // blocking while the remotecallback is unset
+ int duration = callbackReturn.get(15, TimeUnit.SECONDS);
- //wait for SecondActivity animation to complete
- synchronized(CVE_2021_0339.class){
- if(!testCompleted)
- CVE_2021_0339.class.wait();
- }
- Log.d(TAG, "test completed");
+ // if we couldn't get the duration of secondactivity in firstactivity, the default is -1
+ assumeThat(duration, not(equals(-1)));
- //A duration of a transition from "FirstActivity" to "Second Activity"
- //is set in this test to 10 seconds
- // (res/anim/translate1.xml and res/anim/translate2.xml)
- //The fix is supposed to limit the duration to 3000 ms.
- // testing for > 8s
- assertThat(SecondActivity.duration,
- lessThan(MAX_TRANSITION_DURATION_MS + TIME_MEASUREMENT_DELAY_MS));
+ // the max duration after the fix is 3 seconds.
+ // we check to see that the total duration was less than 8 seconds after accounting for lag
+ assertThat(duration,
+ lessThan(MAX_TRANSITION_DURATION_MS + TIME_MEASUREMENT_DELAY_MS));
}
+ /**
+ * create an activity so that the second activity has something to animate from
+ * start the second activity and get the result
+ * return the result from the second activity in the remotecallback
+ */
public static class FirstActivity extends Activity {
+ private RemoteCallback cb;
- @Override
- public void onEnterAnimationComplete() {
- super.onEnterAnimationComplete();
- Intent intent = new Intent(this, SecondActivity.class);
- intent.putExtra("STARTED_TIMESTAMP", SystemClock.uptimeMillis());
- startActivity(intent);
- overridePendingTransition(R.anim.translate2,R.anim.translate1);
- Log.d(TAG,this.getLocalClassName()+" onEnterAnimationComplete()");
- }
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ cb = (RemoteCallback) getIntent().getExtras().get(CALLBACK_KEY);
+ }
+
+ @Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ Intent intent = new Intent(this, SecondActivity.class);
+ intent.putExtra("STARTED_TIMESTAMP", SystemClock.uptimeMillis());
+ startActivityForResult(intent, DURATION_RESULT_CODE);
+ overridePendingTransition(R.anim.translate2,R.anim.translate1);
+ Log.d(TAG,this.getLocalClassName()+" onEnterAnimationComplete()");
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode,int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == DURATION_RESULT_CODE && resultCode == RESULT_OK) {
+ // this is the result that we requested
+ int duration = data.getIntExtra("duration", -1); // get result from secondactivity
+ Bundle res = new Bundle();
+ res.putInt(RESULT_KEY, duration);
+ finish();
+ cb.sendResult(res); // update callback in test
+ }
+ }
}
+ /**
+ * measure time since secondactivity start to secondactivity animation complete
+ * return the duration in the result
+ */
public static class SecondActivity extends Activity{
- public static int duration = 0;
-
- @Override
- public void onEnterAnimationComplete() {
- super.onEnterAnimationComplete();
- long completedTs = SystemClock.uptimeMillis();
- long startedTs = getIntent().getLongExtra("STARTED_TIMESTAMP", 0);
- duration = (int)(completedTs - startedTs);
- Log.d(TAG, this.getLocalClassName()
- + " onEnterAnimationComplete() duration=" + Long.toString(duration));
-
- //Notify main thread that the test is completed
- synchronized(CVE_2021_0339.class){
- CVE_2021_0339.testCompleted = true;
- CVE_2021_0339.class.notifyAll();
+ @Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ long completedTs = SystemClock.uptimeMillis();
+ long startedTs = getIntent().getLongExtra("STARTED_TIMESTAMP", 0);
+ int duration = (int)(completedTs - startedTs);
+ Log.d(TAG, this.getLocalClassName()
+ + " onEnterAnimationComplete() duration=" + Long.toString(duration));
+ Intent durationIntent = new Intent();
+ durationIntent.putExtra("duration", duration);
+ setResult(RESULT_OK, durationIntent); // set result for firstactivity
+ finish(); // firstactivity only gets the result when we finish
}
- }
}
}
diff --git a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
index 0e0657a..cbed06d 100644
--- a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
+++ b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
@@ -149,7 +149,10 @@
"com.android.apex.cts.shim",
// Oom Catcher package to prevent tests from ooming device.
- "com.android.cts.oomcatcher"
+ "com.android.cts.oomcatcher",
+
+ // Collects device info at the start of the test
+ "com.android.compatibility.common.deviceinfo"
));
diff --git a/tests/tests/simphonebookprovider/Android.bp b/tests/tests/simphonebookprovider/Android.bp
index 95580a8..acbff92 100644
--- a/tests/tests/simphonebookprovider/Android.bp
+++ b/tests/tests/simphonebookprovider/Android.bp
@@ -1,7 +1,8 @@
filegroup {
name: "cts-simphonebook-rules-sources",
srcs: [
- "src/android/provider/cts/simphonebook/SimCleanupRule.java",
+ "src/android/provider/cts/simphonebook/RemovableSims.java",
+ "src/android/provider/cts/simphonebook/SimsCleanupRule.java",
"src/android/provider/cts/simphonebook/SimPhonebookRequirementsRule.java",
"src/android/provider/cts/simphonebook/SimsPowerRule.java",
],
diff --git a/tests/tests/simphonebookprovider/AndroidManifest.xml b/tests/tests/simphonebookprovider/AndroidManifest.xml
index 07c1bd4..79db286 100644
--- a/tests/tests/simphonebookprovider/AndroidManifest.xml
+++ b/tests/tests/simphonebookprovider/AndroidManifest.xml
@@ -23,7 +23,7 @@
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
- <application>
+ <application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/tests/simphonebookprovider/nosim/AndroidManifest.xml b/tests/tests/simphonebookprovider/nosim/AndroidManifest.xml
index afdf90e..137759b 100644
--- a/tests/tests/simphonebookprovider/nosim/AndroidManifest.xml
+++ b/tests/tests/simphonebookprovider/nosim/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.provider.cts.simphonebook.nosim">
- <application>
+ <application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/RemovableSims.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/RemovableSims.java
new file mode 100644
index 0000000..a014931
--- /dev/null
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/RemovableSims.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.simphonebook;
+
+import android.Manifest;
+import android.content.Context;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/** Provides the SubscriptionInfo and slot count for removable SIM cards. */
+class RemovableSims {
+ private final Context mContext;
+ private final TelephonyManager mTelephonyManager;
+
+ private List<SubscriptionInfo> mRemovableSubscriptionInfos;
+ private int mRemovableSimSlotCount;
+
+ public RemovableSims(Context context) {
+ mContext = context;
+ mTelephonyManager = Objects.requireNonNull(
+ context.getSystemService(TelephonyManager.class));
+ }
+
+ private synchronized void initialize() {
+ SubscriptionManager subscriptionManager = Objects.requireNonNull(
+ mContext.getSystemService(SubscriptionManager.class));
+ mRemovableSubscriptionInfos = new ArrayList<>();
+ mRemovableSimSlotCount = 0;
+
+ // Wait for the eSIM state to be loaded before continuing. Otherwise, the card info
+ // we load later may indicate that they are not eSIM when they actually are. This works
+ // on phones without eSIM because it will return TelephonyManager.UNSUPPORTED_CARD_ID
+ PollingCheck.waitFor(30_000, () ->
+ mTelephonyManager.getCardIdForDefaultEuicc() !=
+ TelephonyManager.UNINITIALIZED_CARD_ID
+ );
+
+ List<UiccCardInfo> uiccCards = SystemUtil.runWithShellPermissionIdentity(
+ mTelephonyManager::getUiccCardsInfo,
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+
+ List<SubscriptionInfo> allSubscriptions = SystemUtil.runWithShellPermissionIdentity(() ->
+ subscriptionManager.getActiveSubscriptionInfoList(),
+ Manifest.permission.READ_PHONE_STATE);
+ Map<Integer, List<SubscriptionInfo>> subscriptionsByCardId = allSubscriptions.stream()
+ .collect(Collectors.groupingBy(SubscriptionInfo::getCardId));
+ for (UiccCardInfo cardInfo : uiccCards) {
+ if (!cardInfo.isRemovable() || cardInfo.isEuicc()) {
+ continue;
+ }
+ mRemovableSimSlotCount++;
+
+ List<SubscriptionInfo> listWithSubscription = subscriptionsByCardId
+ .getOrDefault(cardInfo.getCardId(), Collections.emptyList());
+ // There should only be 1 in the list but using addAll simplifies things because we
+ // don't have to check for the empty case.
+ mRemovableSubscriptionInfos.addAll(listWithSubscription);
+ }
+ }
+
+ public List<SubscriptionInfo> getSubscriptionInfoForRemovableSims() {
+ if (mRemovableSubscriptionInfos == null) {
+ initialize();
+ }
+ return mRemovableSubscriptionInfos;
+ }
+
+ public int getRemovableSimSlotCount() {
+ if (mRemovableSubscriptionInfos == null) {
+ initialize();
+ }
+ return mRemovableSimSlotCount;
+ }
+
+ public int getDefaultSubscriptionId() {
+ List<SubscriptionInfo> removableSubscriptionInfos = getSubscriptionInfoForRemovableSims();
+ int subscriptionId = SubscriptionManager.getDefaultSubscriptionId();
+ if (removableSubscriptionInfos.stream().anyMatch(
+ info -> info.getSubscriptionId() == subscriptionId)) {
+ return subscriptionId;
+ } else if (!removableSubscriptionInfos.isEmpty()) {
+ return removableSubscriptionInfos.get(0).getSubscriptionId();
+ }
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+}
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimCleanupRule.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimCleanupRule.java
deleted file mode 100644
index 1428a2c..0000000
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimCleanupRule.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.provider.cts.simphonebook;
-
-import android.Manifest;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.provider.SimPhonebookContract;
-import android.provider.SimPhonebookContract.ElementaryFiles;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.util.Log;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.rules.ExternalResource;
-
-/** Removes all records from the SIM card after tests have run. */
-class SimCleanupRule extends ExternalResource {
- private static final String TAG = SimCleanupRule.class.getSimpleName();
- private final ContentResolver mResolver;
- private final int mSubscriptionId;
- private final int mEfType;
- private final Bundle mExtras = new Bundle();
-
- SimCleanupRule(int efType) {
- this(efType, null);
- }
-
- SimCleanupRule(int efType, String pin2) {
- this(efType, pin2, SubscriptionManager.getDefaultSubscriptionId());
- }
-
- SimCleanupRule(int efType, String pin2, int subscriptionId) {
- Context context = ApplicationProvider.getApplicationContext();
- mResolver = context.getContentResolver();
- mEfType = efType;
- mExtras.putString(SimPhonebookContract.SimRecords.QUERY_ARG_PIN2, pin2);
- mSubscriptionId = subscriptionId;
- }
-
- public static SimCleanupRule forAdnOnSlot(int slotIndex) {
- Context context = ApplicationProvider.getApplicationContext();
- SubscriptionInfo info = null;
- try {
- info = SystemUtil.callWithShellPermissionIdentity(
- () -> context.getSystemService(SubscriptionManager.class)
- .getActiveSubscriptionInfoForSimSlotIndex(slotIndex),
- Manifest.permission.READ_PHONE_STATE);
- } catch (Exception e) {
- Log.e(TAG, "Failed to get subscription", e);
- }
- return new SimCleanupRule(
- ElementaryFiles.EF_ADN, null,
- info != null
- ? info.getSubscriptionId()
- : SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- }
-
- int getSubscriptionId() {
- return mSubscriptionId;
- }
-
- void setPin2(String pin2) {
- mExtras.putString(SimPhonebookContract.SimRecords.QUERY_ARG_PIN2, pin2);
- }
-
- @Override
- protected void after() {
- if (mSubscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- Log.w(TAG, "No cleanup for invalid subscription ID");
- return;
- }
- if (mEfType == ElementaryFiles.EF_FDN) {
- clearFdn();
- } else {
- clearEf();
- }
- }
-
- private void clearFdn() {
- SystemUtil.runWithShellPermissionIdentity(this::clearEf,
- Manifest.permission.MODIFY_PHONE_STATE);
- }
-
- private void clearEf() {
- try (Cursor cursor = mResolver.query(
- SimPhonebookContract.SimRecords.getContentUri(mSubscriptionId, mEfType),
- new String[]{SimPhonebookContract.SimRecords.RECORD_NUMBER}, null, null)) {
- while (cursor.moveToNext()) {
- mResolver.delete(
- SimPhonebookContract.SimRecords.getItemUri(mSubscriptionId, mEfType,
- cursor.getInt(0)), mExtras);
- }
- }
- }
-}
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java
index 0e3b6e7..0cfaa44 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java
@@ -22,6 +22,7 @@
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
@@ -29,7 +30,6 @@
import android.os.Looper;
import android.provider.SimPhonebookContract;
import android.provider.SimPhonebookContract.ElementaryFiles;
-import android.telephony.SubscriptionManager;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
@@ -57,7 +57,7 @@
@ClassRule
public static final SimsPowerRule SIMS_POWER_RULE = SimsPowerRule.on();
- private final SimCleanupRule mSimCleanupRule = new SimCleanupRule(ElementaryFiles.EF_ADN);
+ private final SimsCleanupRule mSimCleanupRule = new SimsCleanupRule(ElementaryFiles.EF_ADN);
@Rule
public final TestRule mRule = RuleChain
.outerRule(new RequiredFeatureRule(PackageManager.FEATURE_TELEPHONY))
@@ -70,7 +70,8 @@
@Before
public void setUp() {
- mResolver = ApplicationProvider.getApplicationContext().getContentResolver();
+ Context context = ApplicationProvider.getApplicationContext();
+ mResolver = context.getContentResolver();
mObserver = new RecordingContentObserver();
mResolver.registerContentObserver(SimPhonebookContract.AUTHORITY_URI, false, mObserver);
assertThat(mObserver.observed).isEmpty();
@@ -78,7 +79,7 @@
// Make sure the provider has been created.
mResolver.getType(SimPhonebookContract.SimRecords.getContentUri(1, EF_ADN));
- mSubId = SubscriptionManager.getDefaultSubscriptionId();
+ mSubId = new RemovableSims(context).getDefaultSubscriptionId();
}
@After
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ElementaryFilesTest.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ElementaryFilesTest.java
index acdb215..5fdfa8e 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ElementaryFilesTest.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ElementaryFilesTest.java
@@ -26,6 +26,7 @@
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
@@ -70,14 +71,14 @@
public final TestRule mRule = RuleChain
.outerRule(new RequiredFeatureRule(PackageManager.FEATURE_TELEPHONY))
.around(new SimPhonebookRequirementsRule())
- .around(new SimCleanupRule(ElementaryFiles.EF_ADN));
+ .around(new SimsCleanupRule(ElementaryFiles.EF_ADN));
@Before
public void setUp() {
- mResolver = ApplicationProvider.getApplicationContext().getContentResolver();
- mSubscriptionManager = ApplicationProvider.getApplicationContext().getSystemService(
- SubscriptionManager.class);
- mValidSubscriptionId = SubscriptionManager.getDefaultSubscriptionId();
+ Context context = ApplicationProvider.getApplicationContext();
+ mResolver = context.getContentResolver();
+ mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+ mValidSubscriptionId = new RemovableSims(context).getDefaultSubscriptionId();
}
@Test
@@ -98,7 +99,7 @@
ElementaryFiles.getItemUri(subscriptionId, ElementaryFiles.EF_FDN),
new String[]{ElementaryFiles.SUBSCRIPTION_ID}, null, null)) {
// If the EF is unsupported the item Uri will return an empty cursor
- if (cursor.moveToFirst()) {
+ if (Objects.requireNonNull(cursor).moveToFirst()) {
assertWithMessage("subscriptionId")
.that(subscriptionId).isEqualTo(cursor.getInt(0));
fdnSupportedBySubId.append(subscriptionId, true);
@@ -108,7 +109,7 @@
ElementaryFiles.getItemUri(subscriptionId, ElementaryFiles.EF_SDN),
new String[]{ElementaryFiles.SUBSCRIPTION_ID}, null, null)) {
// If the EF is unsupported the item Uri will return an empty cursor
- if (cursor.moveToFirst()) {
+ if (Objects.requireNonNull(cursor).moveToFirst()) {
assertWithMessage("subscriptionId")
.that(subscriptionId).isEqualTo(cursor.getInt(0));
sdnSupportedBySubId.append(subscriptionId, true);
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsMultiSimTest.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsMultiSimTest.java
index 9331fc9..df7a20b 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsMultiSimTest.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsMultiSimTest.java
@@ -24,10 +24,12 @@
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.SimPhonebookContract.ElementaryFiles;
import android.provider.SimPhonebookContract.SimRecords;
+import android.telephony.SubscriptionInfo;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
@@ -40,17 +42,18 @@
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import java.util.List;
import java.util.Objects;
/** Tests of {@link SimRecords} for devices that have multiple SIM cards. */
@RunWith(AndroidJUnit4.class)
public class SimPhonebookContract_SimRecordsMultiSimTest {
- private final SimCleanupRule mSim1Rule = SimCleanupRule.forAdnOnSlot(0);
- private final SimCleanupRule mSim2Rule = SimCleanupRule.forAdnOnSlot(1);
+ private final SimPhonebookRequirementsRule mRequirementsRule =
+ new SimPhonebookRequirementsRule(2);
@Rule
- public final TestRule mRule = RuleChain.outerRule(new SimPhonebookRequirementsRule(2))
- .around(mSim1Rule).around(mSim2Rule);
+ public final TestRule mRule = RuleChain.outerRule(mRequirementsRule)
+ .around(new SimsCleanupRule(EF_ADN));
private ContentResolver mResolver;
@@ -59,9 +62,12 @@
@Before
public void setUp() {
- mResolver = ApplicationProvider.getApplicationContext().getContentResolver();
- mSubscriptionId1 = mSim1Rule.getSubscriptionId();
- mSubscriptionId2 = mSim2Rule.getSubscriptionId();
+ Context context = ApplicationProvider.getApplicationContext();
+ mResolver = context.getContentResolver();
+ RemovableSims removableSims = new RemovableSims(context);
+ List<SubscriptionInfo> infos = removableSims.getSubscriptionInfoForRemovableSims();
+ mSubscriptionId1 = infos.get(0).getSubscriptionId();
+ mSubscriptionId2 = infos.get(1).getSubscriptionId();
}
@Test
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java
index 1592b26..37ea8f6 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java
@@ -37,7 +37,6 @@
import android.provider.SimPhonebookContract.ElementaryFiles;
import android.provider.SimPhonebookContract.SimRecords;
import android.telephony.PhoneNumberUtils;
-import android.telephony.SubscriptionManager;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
@@ -72,9 +71,8 @@
*/
private String mPin2 = "1234";
- private final SimCleanupRule mAdnCleanupRule = new SimCleanupRule(ElementaryFiles.EF_ADN);
- private final SimCleanupRule mFdnCleanupRule = new SimCleanupRule(ElementaryFiles.EF_FDN,
- mPin2);
+ private final SimsCleanupRule mAdnCleanupRule = new SimsCleanupRule(ElementaryFiles.EF_ADN);
+ private final SimsCleanupRule mFdnCleanupRule = new SimsCleanupRule(ElementaryFiles.EF_FDN);
@Rule
public final TestRule mRule = RuleChain
.outerRule(new RequiredFeatureRule(PackageManager.FEATURE_TELEPHONY))
@@ -97,7 +95,7 @@
public void setUp() {
mContext = ApplicationProvider.getApplicationContext();
mResolver = mContext.getContentResolver();
- mDefaultSubscriptionId = SubscriptionManager.getDefaultSubscriptionId();
+ mDefaultSubscriptionId = new RemovableSims(mContext).getDefaultSubscriptionId();
Bundle args = InstrumentationRegistry.getArguments();
if (args.containsKey("sim-pin2")) {
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookRequirementsRule.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookRequirementsRule.java
index 6446bee..e53694b 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookRequirementsRule.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookRequirementsRule.java
@@ -21,21 +21,12 @@
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.junit.Assume.assumeThat;
-import android.Manifest;
import android.content.Context;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.telephony.UiccSlotInfo;
import androidx.test.core.app.ApplicationProvider;
-import com.android.compatibility.common.util.SystemUtil;
-
import org.junit.rules.ExternalResource;
-import java.util.Arrays;
-import java.util.Objects;
-
class SimPhonebookRequirementsRule extends ExternalResource {
private final int mMinimumSimCount;
@@ -51,23 +42,12 @@
@Override
protected void before() {
Context context = ApplicationProvider.getApplicationContext();
- TelephonyManager telephonyManager = Objects.requireNonNull(
- context.getSystemService(TelephonyManager.class));
- UiccSlotInfo[] uiccSlots = SystemUtil.runWithShellPermissionIdentity(
- telephonyManager::getUiccSlotsInfo,
- Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
- int nonEsimCount = (int) Arrays.stream(uiccSlots)
- .filter(info -> !info.getIsEuicc()).count();
- assumeThat(nonEsimCount, greaterThanOrEqualTo(mMinimumSimCount));
+ RemovableSims removableSims = new RemovableSims(context);
- SubscriptionManager subscriptionManager = Objects.requireNonNull(
- context.getSystemService(SubscriptionManager.class));
- SystemUtil.runWithShellPermissionIdentity(() ->
- // This is an assertion rather than an assumption because according to the
- // CTS setup all slots should have a SIM installed.
- assertWithMessage("A SIM must be installed in each SIM slot.").that(
- subscriptionManager.getActiveSubscriptionInfoCount())
- .isEqualTo(telephonyManager.getSupportedModemCount())
- );
+ assumeThat(removableSims.getRemovableSimSlotCount(),
+ greaterThanOrEqualTo(mMinimumSimCount));
+ assertWithMessage("A SIM must be installed in each SIM slot")
+ .that(removableSims.getSubscriptionInfoForRemovableSims().size())
+ .isAtLeast(mMinimumSimCount);
}
}
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimsCleanupRule.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimsCleanupRule.java
new file mode 100644
index 0000000..5bd519c
--- /dev/null
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimsCleanupRule.java
@@ -0,0 +1,84 @@
+/*
+ * 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.provider.cts.simphonebook;
+
+import android.Manifest;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.SimPhonebookContract;
+import android.provider.SimPhonebookContract.ElementaryFiles;
+import android.telephony.SubscriptionInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.rules.ExternalResource;
+
+import java.util.List;
+import java.util.Objects;
+
+/** Removes all records from all the removalbe SIM cards after tests have run. */
+class SimsCleanupRule extends ExternalResource {
+ private static final String TAG = SimsCleanupRule.class.getSimpleName();
+ private final ContentResolver mResolver;
+ private final int mEfType;
+ private final Bundle mExtras = new Bundle();
+
+ SimsCleanupRule(int efType) {
+ Context context = ApplicationProvider.getApplicationContext();
+ mResolver = context.getContentResolver();
+ mEfType = efType;
+ }
+
+ void setPin2(String pin2) {
+ mExtras.putString(SimPhonebookContract.SimRecords.QUERY_ARG_PIN2, pin2);
+ }
+
+ @Override
+ protected void after() {
+ RemovableSims removableSims = new RemovableSims(
+ ApplicationProvider.getApplicationContext());
+ List<SubscriptionInfo> infos = removableSims.getSubscriptionInfoForRemovableSims();
+ for (SubscriptionInfo info : infos) {
+ if (mEfType == ElementaryFiles.EF_FDN) {
+ clearFdn(info.getSubscriptionId());
+ } else {
+ clearEf(info.getSubscriptionId());
+ }
+ }
+ }
+
+ private void clearFdn(int subscriptionId) {
+ SystemUtil.runWithShellPermissionIdentity(() -> clearEf(subscriptionId),
+ Manifest.permission.MODIFY_PHONE_STATE);
+ }
+
+ private void clearEf(int subscriptionId) {
+ try (Cursor cursor = Objects.requireNonNull(mResolver.query(
+ SimPhonebookContract.SimRecords.getContentUri(subscriptionId, mEfType),
+ new String[]{SimPhonebookContract.SimRecords.RECORD_NUMBER}, null, null))) {
+ while (cursor.moveToNext()) {
+ mResolver.delete(
+ SimPhonebookContract.SimRecords.getItemUri(subscriptionId, mEfType,
+ cursor.getInt(0)), mExtras);
+ }
+ }
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java b/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
index e01b12b..4272af0 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
@@ -69,8 +69,6 @@
protected void tearDown() throws Exception {
if (mShouldTestTelecom && !TextUtils.isEmpty(mPreviousDefaultDialer)) {
TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
- mTelecomManager.unregisterPhoneAccount(TEST_PHONE_ACCOUNT_HANDLE);
- CtsConnectionService.tearDown();
MockCallScreeningService.disableService(mContext);
}
super.tearDown();
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index aa7224a..abc0888 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -246,6 +246,7 @@
protected PhoneAccount setupConnectionService(MockConnectionService connectionService,
int flags) throws Exception {
+ Log.i(TAG, "Setting up mock connection service");
if (connectionService != null) {
this.connectionService = connectionService;
} else {
@@ -276,6 +277,7 @@
}
protected void tearDownConnectionService(PhoneAccountHandle accountHandle) throws Exception {
+ Log.i(TAG, "Tearing down mock connection service");
if (this.connectionService != null) {
assertNumConnections(this.connectionService, 0);
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
index 48cc464..cacf403 100755
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
@@ -25,6 +25,8 @@
import android.media.AudioManager;
import android.net.Uri;
import android.telecom.Call;
+import android.telecom.CallScreeningService;
+import android.telecom.CallScreeningService.CallResponse;
import android.telecom.Connection;
import android.telecom.ConnectionService;
import android.telecom.PhoneAccountHandle;
@@ -291,7 +293,22 @@
}
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity("android.permission.MODIFY_PHONE_STATE");
+ MockCallScreeningService.enableService(mContext);
try {
+ CallScreeningService.CallResponse response =
+ new CallScreeningService.CallResponse.Builder()
+ .setDisallowCall(false)
+ .setRejectCall(false)
+ .setSilenceCall(false)
+ .setSkipCallLog(false)
+ .setSkipNotification(false)
+ .setShouldScreenCallViaAudioProcessing(false)
+ .setCallComposerAttachmentsToShow(
+ CallResponse.CALL_COMPOSER_ATTACHMENT_PRIORITY
+ | CallResponse.CALL_COMPOSER_ATTACHMENT_SUBJECT)
+ .build();
+ MockCallScreeningService.setCallbacks(createCallbackForCsTest(response));
+
addAndVerifyNewIncomingCall(createTestNumber(), null);
MockConnection connection = verifyConnectionForIncomingCall();
@@ -300,9 +317,12 @@
.getArgs(0);
assertFalse((boolean) callFilteringCompleteInvocations[0]);
assertFalse((boolean) callFilteringCompleteInvocations[1]);
+ assertEquals(response, callFilteringCompleteInvocations[2]);
+ assertFalse((boolean) callFilteringCompleteInvocations[3]);
} finally {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
+ MockCallScreeningService.disableService(mContext);
}
}
@@ -315,8 +335,25 @@
Uri testNumber = createTestNumber();
Uri contactUri = TestUtils.insertContact(mContext.getContentResolver(),
testNumber.getSchemeSpecificPart());
+ TestUtils.setSystemDialerOverride(getInstrumentation());
+ MockCallScreeningService.enableService(mContext);
try {
+ CallScreeningService.CallResponse response =
+ new CallScreeningService.CallResponse.Builder()
+ .setDisallowCall(false)
+ .setRejectCall(false)
+ .setSilenceCall(false)
+ .setSkipCallLog(false)
+ .setSkipNotification(false)
+ .setShouldScreenCallViaAudioProcessing(false)
+ .setCallComposerAttachmentsToShow(
+ CallResponse.CALL_COMPOSER_ATTACHMENT_PRIORITY
+ | CallResponse.CALL_COMPOSER_ATTACHMENT_SUBJECT)
+ .build();
+ MockCallScreeningService.setCallbacks(createCallbackForCsTest(response));
+
addAndVerifyNewIncomingCall(testNumber, null);
+
MockConnection connection = verifyConnectionForIncomingCall();
Object[] callFilteringCompleteInvocations =
@@ -324,12 +361,28 @@
.getArgs(0);
assertFalse((boolean) callFilteringCompleteInvocations[0]);
assertTrue((boolean) callFilteringCompleteInvocations[1]);
+ assertEquals(response, callFilteringCompleteInvocations[2]);
+ assertTrue((boolean) callFilteringCompleteInvocations[3]);
} finally {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
TestUtils.deleteContact(mContext.getContentResolver(), contactUri);
+ MockCallScreeningService.disableService(mContext);
+ TestUtils.clearSystemDialerOverride(getInstrumentation());
}
}
+
+ private MockCallScreeningService.CallScreeningServiceCallbacks createCallbackForCsTest(
+ CallScreeningService.CallResponse response) {
+ return new MockCallScreeningService.CallScreeningServiceCallbacks() {
+ @Override
+ public void onScreenCall(Call.Details callDetails) {
+
+ getService().respondToCall(callDetails, response);
+ }
+ };
+ }
+
public void testCallDirectionOutgoing() {
if (!mShouldTestTelecom) {
return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
index 89eb5a6..5dae559 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
@@ -69,7 +69,7 @@
public static void setUp(ConnectionService connectionService) throws Exception {
synchronized(sLock) {
if (sConnectionService != null) {
- throw new Exception("Mock ConnectionService exists. Failed to call tearDown().");
+ throw new Exception("Mock ConnectionService exists. Failed to call setUp().");
}
sConnectionService = connectionService;
}
@@ -90,6 +90,8 @@
return sConnectionService.onCreateOutgoingConnection(
connectionManagerPhoneAccount, request);
} else {
+ Log.e(LOG_TAG,
+ "Tried to create outgoing connection when sConnectionService null!");
return null;
}
}
@@ -103,6 +105,8 @@
return sConnectionService.onCreateIncomingConnection(
connectionManagerPhoneAccount, request);
} else {
+ Log.e(LOG_TAG,
+ "Tried to create incoming connection when sConnectionService null!");
return null;
}
}
@@ -114,6 +118,9 @@
if (sConnectionService != null) {
sConnectionService.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount,
request);
+ } else {
+ Log.e(LOG_TAG,
+ "onCreateIncomingConnectionFailed called when sConnectionService null!");
}
}
@@ -125,6 +132,8 @@
return sConnectionService.onCreateOutgoingConference(connectionManagerPhoneAccount,
request);
} else {
+ Log.e(LOG_TAG,
+ "onCreateOutgoingConference called when sConnectionService null!");
return null;
}
}
@@ -137,6 +146,9 @@
if (sConnectionService != null) {
sConnectionService.onCreateOutgoingConferenceFailed(connectionManagerPhoneAccount,
request);
+ } else {
+ Log.e(LOG_TAG,
+ "onCreateOutgoingConferenceFailed called when sConnectionService null!");
}
}
}
@@ -149,6 +161,8 @@
return sConnectionService.onCreateIncomingConference(connectionManagerPhoneAccount,
request);
} else {
+ Log.e(LOG_TAG,
+ "onCreateIncomingConference called when sConnectionService null!");
return null;
}
}
@@ -161,6 +175,9 @@
if (sConnectionService != null) {
sConnectionService.onCreateIncomingConferenceFailed(connectionManagerPhoneAccount,
request);
+ } else {
+ Log.e(LOG_TAG,
+ "onCreateIncomingConferenceFailed called when sConnectionService null!");
}
}
}
@@ -170,6 +187,9 @@
synchronized(sLock) {
if (sConnectionService != null) {
sConnectionService.onConference(connection1, connection2);
+ } else {
+ Log.e(LOG_TAG,
+ "onConference called when sConnectionService null!");
}
}
}
@@ -179,6 +199,9 @@
synchronized(sLock) {
if (sConnectionService != null) {
sConnectionService.onRemoteExistingConnectionAdded(connection);
+ } else {
+ Log.e(LOG_TAG,
+ "onRemoteExistingConnectionAdded called when sConnectionService null!");
}
}
}
@@ -247,6 +270,9 @@
synchronized(sLock) {
if (sConnectionService != null) {
sConnectionService.onRemoteConferenceAdded(conference);
+ } else {
+ Log.e(LOG_TAG,
+ "onRemoteConferenceAdded called when sConnectionService null!");
}
}
}
@@ -256,6 +282,9 @@
synchronized (sLock) {
if (sConnectionService != null) {
sConnectionService.onConnectionServiceFocusGained();
+ } else {
+ Log.e(LOG_TAG,
+ "onConnectionServiceFocusGained called when sConnectionService null!");
}
}
}
@@ -265,6 +294,9 @@
synchronized (sLock) {
if (sConnectionService != null) {
sConnectionService.onConnectionServiceFocusLost();
+ } else {
+ Log.e(LOG_TAG,
+ "onConnectionServiceFocusLost called when sConnectionService null!");
}
}
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
index ff81c97..f46fd10 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
@@ -24,10 +24,12 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Configuration;
+import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.telecom.CallAudioState;
import android.telecom.Call;
+import android.telecom.CallScreeningService;
import android.telecom.Connection;
import android.telecom.ConnectionService;
import android.telecom.InCallService;
@@ -407,6 +409,52 @@
}
}
+ public void testCallComposerAttachmentsStrippedCorrectly() throws Exception {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+ Bundle extras = new Bundle();
+ extras.putParcelable(TelecomManager.EXTRA_LOCATION, new Location(""));
+ extras.putInt(TelecomManager.EXTRA_PRIORITY, TelecomManager.PRIORITY_URGENT);
+ extras.putString(TelecomManager.EXTRA_CALL_SUBJECT, "blah blah blah");
+
+ TestUtils.setSystemDialerOverride(getInstrumentation());
+ MockCallScreeningService.enableService(mContext);
+ try {
+ CallScreeningService.CallResponse response =
+ new CallScreeningService.CallResponse.Builder()
+ .setDisallowCall(false)
+ .setRejectCall(false)
+ .setSilenceCall(false)
+ .setSkipCallLog(false)
+ .setSkipNotification(false)
+ .setShouldScreenCallViaAudioProcessing(false)
+ .setCallComposerAttachmentsToShow(0)
+ .build();
+
+ MockCallScreeningService.setCallbacks(
+ new MockCallScreeningService.CallScreeningServiceCallbacks() {
+ @Override
+ public void onScreenCall(Call.Details callDetails) {
+ getService().respondToCall(callDetails, response);
+ }
+ });
+
+ addAndVerifyNewIncomingCall(createTestNumber(), extras);
+ verifyConnectionForIncomingCall(0);
+ MockInCallService inCallService = mInCallCallbacks.getService();
+ Call call = inCallService.getLastCall();
+
+ assertFalse(call.getDetails().getExtras().containsKey(TelecomManager.EXTRA_LOCATION));
+ assertFalse(call.getDetails().getExtras().containsKey(TelecomManager.EXTRA_PRIORITY));
+ assertFalse(call.getDetails().getExtras()
+ .containsKey(TelecomManager.EXTRA_CALL_SUBJECT));
+ } finally {
+ MockCallScreeningService.disableService(mContext);
+ TestUtils.clearSystemDialerOverride(getInstrumentation());
+ }
+ }
+
private Uri blockNumber(Uri phoneNumberUri) {
Uri number = insertBlockedNumber(mContext, phoneNumberUri.getSchemeSpecificPart());
if (number == null) {
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
index 62d3916..c6ef75c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
@@ -21,6 +21,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
@@ -261,8 +262,11 @@
}
@Override
- public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) {
- getInvokeCounter(ON_CALL_FILTERING_COMPLETED).invoke(isBlocked, isInContacts);
+ public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+ CallScreeningService.CallResponse response,
+ boolean responseFromSystemDialer) {
+ getInvokeCounter(ON_CALL_FILTERING_COMPLETED).invoke(isBlocked, isInContacts,
+ response, responseFromSystemDialer);
}
public int getCurrentState() {
diff --git a/tests/tests/telecom/src/android/telecom/cts/NonUiInCallServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/NonUiInCallServiceTest.java
index 94ec4d0..cb1357a 100644
--- a/tests/tests/telecom/src/android/telecom/cts/NonUiInCallServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/NonUiInCallServiceTest.java
@@ -31,7 +31,6 @@
protected void tearDown() throws Exception {
if (mShouldTestTelecom) {
mTelecomManager.unregisterPhoneAccount(TEST_PHONE_ACCOUNT_HANDLE);
- CtsConnectionService.tearDown();
}
super.tearDown();
waitOnAllHandlers(getInstrumentation());
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 132a88d..0805481 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -164,6 +164,19 @@
</intent-filter>
</service>
+ <!-- Activity that allows the user to trigger the UCE APIs -->
+ <activity android:name="android.telephony.ims.cts.UceActivity"
+ android:exported="false">
+ <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="android.telephony.ims.cts.action_finish"/>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+ </intent-filter>
+ </activity>
+
<service
android:name="android.telephony.gba.cts.TestGbaService"
android:directBootAware="true"
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 90bc4b8..97c8a51 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -337,23 +337,29 @@
// verify that REBROADCAST_ON_UNLOCK is populated
assertFalse(
intent.getBooleanExtra(CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK,
- true));
+ true));
}
}
};
- final IntentFilter filter =
- new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
- getContext().registerReceiver(receiver, filter);
+ try {
+ final IntentFilter filter =
+ new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ getContext().registerReceiver(receiver, filter);
- // verify that carrier config is received
- int subId = SubscriptionManager.getDefaultSubscriptionId();
- getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
- mConfigManager.notifyConfigChangedForSubId(subId);
+ // verify that carrier config is received
+ int subId = SubscriptionManager.getDefaultSubscriptionId();
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+ mConfigManager.notifyConfigChangedForSubId(subId);
- Boolean broadcastReceived = queue.poll(BROADCAST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- assertNotNull(broadcastReceived);
- assertTrue(broadcastReceived);
+ Boolean broadcastReceived = queue.poll(BROADCAST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ assertNotNull(broadcastReceived);
+ assertTrue(broadcastReceived);
+ } finally {
+ // unregister receiver
+ getContext().unregisterReceiver(receiver);
+ receiver = null;
+ }
}
@Test
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
index a9a75b3..ea36271 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
@@ -29,6 +29,7 @@
import android.telephony.data.ApnSetting;
import android.telephony.data.DataCallResponse;
import android.telephony.data.SliceInfo;
+import android.telephony.data.TrafficDescriptor;
import org.junit.Test;
@@ -66,7 +67,10 @@
.setMappedHplmnSliceDifferentiator(TEST_HPLMN_SLICE_DIFFERENTIATOR)
.setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
.build();
-
+ private static final String DNN = "DNN";
+ private static final String OS_APP_ID = "OS_APP_ID";
+ private static final List<TrafficDescriptor> TRAFFIC_DESCRIPTORS =
+ Arrays.asList(new TrafficDescriptor(DNN, OS_APP_ID));
@Test
public void testConstructorAndGetters() {
@@ -86,6 +90,7 @@
.setHandoverFailureMode(HANDOVER_FAILURE_MODE)
.setPduSessionId(PDU_SESSION_ID)
.setSliceInfo(SLICE_INFO)
+ .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
.build();
assertThat(response.getCause()).isEqualTo(CAUSE);
@@ -103,6 +108,7 @@
assertThat(response.getHandoverFailureMode()).isEqualTo(HANDOVER_FAILURE_MODE_DO_FALLBACK);
assertThat(response.getPduSessionId()).isEqualTo(PDU_SESSION_ID);
assertThat(response.getSliceInfo()).isEqualTo(SLICE_INFO);
+ assertThat(response.getTrafficDescriptors()).isEqualTo(TRAFFIC_DESCRIPTORS);
}
@Test
@@ -123,6 +129,7 @@
.setHandoverFailureMode(HANDOVER_FAILURE_MODE)
.setPduSessionId(PDU_SESSION_ID)
.setSliceInfo(SLICE_INFO)
+ .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
.build();
DataCallResponse equalsResponse = new DataCallResponse.Builder()
@@ -141,6 +148,7 @@
.setHandoverFailureMode(HANDOVER_FAILURE_MODE)
.setPduSessionId(PDU_SESSION_ID)
.setSliceInfo(SLICE_INFO)
+ .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
.build();
assertThat(response).isEqualTo(equalsResponse);
@@ -164,6 +172,7 @@
.setHandoverFailureMode(HANDOVER_FAILURE_MODE)
.setPduSessionId(PDU_SESSION_ID)
.setSliceInfo(SLICE_INFO)
+ .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
.build();
DataCallResponse notEqualsResponse = new DataCallResponse.Builder()
@@ -182,6 +191,7 @@
.setHandoverFailureMode(HANDOVER_FAILURE_MODE_LEGACY)
.setPduSessionId(PDU_SESSION_ID)
.setSliceInfo(SLICE_INFO)
+ .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
.build();
assertThat(response).isNotEqualTo(notEqualsResponse);
@@ -207,6 +217,7 @@
.setHandoverFailureMode(HANDOVER_FAILURE_MODE)
.setPduSessionId(PDU_SESSION_ID)
.setSliceInfo(SLICE_INFO)
+ .setTrafficDescriptors(TRAFFIC_DESCRIPTORS)
.build();
Parcel stateParcel = Parcel.obtain();
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
index fe0777e..74fbef9 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
@@ -2288,12 +2288,20 @@
TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER);
}
);
+ long allowedNetworkTypeEnable2g = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> {
+ return tm.getAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+ }
+ );
assertThat(allowedNetworkTypeUser).isEqualTo(
mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
assertThat(allowedNetworkTypePower).isEqualTo(
mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER));
assertThat(allowedNetworkTypeCarrier).isEqualTo(
mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER));
+ assertThat(allowedNetworkTypeEnable2g).isEqualTo(
+ mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G));
// Test unregister
unRegisterPhoneStateListener(mOnAllowedNetworkTypesChangedCalled,
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 58c1d52..7fd15e3 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -160,7 +160,7 @@
private String mSelfCertHash;
private static final int TOLERANCE = 1000;
- private static final int TIMEOUT_FOR_NETWORK_OPS = TOLERANCE * 10;
+ private static final int TIMEOUT_FOR_NETWORK_OPS = TOLERANCE * 180;
private PhoneStateListener mListener;
private static ConnectivityManager mCm;
private static final String TAG = "TelephonyManagerTest";
@@ -380,12 +380,20 @@
TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER);
}
);
+ long allowedNetworkTypesEnable2g = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> {
+ return tm.getAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+ }
+ );
mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,
allowedNetworkTypesUser);
mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER,
allowedNetworkTypesPower);
mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER,
allowedNetworkTypesCarrier);
+ mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
+ allowedNetworkTypesEnable2g);
}
private void recoverAllowedNetworkType() {
@@ -3093,6 +3101,8 @@
long allowedNetworkTypes3 = TelephonyManager.NETWORK_TYPE_BITMASK_NR
| TelephonyManager.NETWORK_TYPE_BITMASK_LTE
| TelephonyManager.NETWORK_TYPE_BITMASK_UMTS;
+ long allowedNetworkTypes4 = TelephonyManager.NETWORK_TYPE_LTE
+ | TelephonyManager.NETWORK_TYPE_EVDO_B;
try {
mIsAllowedNetworkTypeChanged = true;
@@ -3112,6 +3122,11 @@
(tm) -> tm.setAllowedNetworkTypesForReason(
TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER,
allowedNetworkTypes3));
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ mTelephonyManager,
+ (tm) -> tm.setAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
+ allowedNetworkTypes4));
long deviceAllowedNetworkTypes1 = ShellIdentityUtils.invokeMethodWithShellPermissions(
mTelephonyManager, (tm) -> {
return tm.getAllowedNetworkTypesForReason(
@@ -3130,9 +3145,16 @@
TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER);
}
);
+ long deviceAllowedNetworkTypes4 = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mTelephonyManager, (tm) -> {
+ return tm.getAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+ }
+ );
assertEquals(allowedNetworkTypes1, deviceAllowedNetworkTypes1);
assertEquals(allowedNetworkTypes2, deviceAllowedNetworkTypes2);
assertEquals(allowedNetworkTypes3, deviceAllowedNetworkTypes3);
+ assertEquals(allowedNetworkTypes4, deviceAllowedNetworkTypes4);
} catch (SecurityException se) {
fail("testSetAllowedNetworkTypes: SecurityException not expected");
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
new file mode 100644
index 0000000..5ab27d6
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.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.telephony.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.telephony.data.TrafficDescriptor;
+
+import org.junit.Test;
+
+public class TrafficDescriptorTest {
+ private static final String DNN = "DNN";
+ private static final String OS_APP_ID = "OS_APP_ID";
+
+ @Test
+ public void testConstructorAndGetters() {
+ TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
+ assertThat(td.getDnn()).isEqualTo(DNN);
+ assertThat(td.getOsAppId()).isEqualTo(OS_APP_ID);
+ }
+
+ @Test
+ public void testEquals() {
+ TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
+ TrafficDescriptor equalsTd = new TrafficDescriptor(DNN, OS_APP_ID);
+ assertThat(td).isEqualTo(equalsTd);
+ }
+
+ @Test
+ public void testNotEquals() {
+ TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
+ TrafficDescriptor notEqualsTd = new TrafficDescriptor("NOT_DNN", "NOT_OS_APP_ID");
+ assertThat(td).isNotEqualTo(notEqualsTd);
+ assertThat(td).isNotEqualTo(null);
+ }
+
+ @Test
+ public void testParcel() {
+ TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
+
+ Parcel parcel = Parcel.obtain();
+ td.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ TrafficDescriptor parcelTd = TrafficDescriptor.CREATOR.createFromParcel(parcel);
+ assertThat(td).isEqualTo(parcelTd);
+ }
+}
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 2d81500..100282f 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
@@ -1107,7 +1107,14 @@
sServiceConnector.getCarrierService().getRcsFeature()
.notifyCapabilitiesStatusChanged(capabilities);
- // Verify that the publish is triggered and receive the publish state changed callback.
+ CapabilityExchangeEventListener eventListener =
+ sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+ // ImsService triggers to notify framework publish device's capabilities.
+ eventListener.onRequestPublishCapabilities(
+ RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+
+ // Verify ImsService receive the publish request from framework.
assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
TestImsService.LATCH_UCE_REQUEST_PUBLISH));
assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
@@ -1122,9 +1129,6 @@
automan.dropShellPermissionIdentity();
}
- CapabilityExchangeEventListener eventListener =
- sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
-
// ImsService triggers to notify framework publish device's capabilities.
eventListener.onRequestPublishCapabilities(
RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
@@ -1230,7 +1234,14 @@
sServiceConnector.getCarrierService().getRcsFeature()
.notifyCapabilitiesStatusChanged(capabilities);
- // Framework should trigger the device capabilities publish when IMS is registered.
+ CapabilityExchangeEventListener eventListener =
+ sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+ // ImsService triggers to notify framework publish device's capabilities.
+ eventListener.onRequestPublishCapabilities(
+ RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+
+ // Verify the ImsService receive the publish request from framework.
assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
TestImsService.LATCH_UCE_REQUEST_PUBLISH));
assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
@@ -1253,9 +1264,6 @@
cb.onNetworkResponse(networkResp, reason, reasonHeaderCause, reasonHeaderText);
});
- CapabilityExchangeEventListener eventListener =
- sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
-
// ImsService triggers to notify framework publish device's capabilities.
eventListener.onRequestPublishCapabilities(
RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
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 4d07dd6..73d71f6 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
@@ -33,6 +33,7 @@
import static junit.framework.Assert.assertTrue;
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.fail;
@@ -61,6 +62,8 @@
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.stub.CapabilityExchangeEventListener;
+import android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback;
import android.telephony.ims.stub.ImsFeatureConfiguration;
import android.util.Log;
import android.util.Pair;
@@ -68,6 +71,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.compatibility.common.util.BlockedNumberUtil;
import com.android.compatibility.common.util.ShellIdentityUtils;
import org.junit.After;
@@ -93,6 +97,15 @@
@RunWith(AndroidJUnit4.class)
public class RcsUceAdapterTest {
+ private static final String FEATURE_TAG_CHAT =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"";
+ private static final String FEATURE_TAG_FILE_TRANSFER =
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"";
+ private static final String FEATURE_TAG_MMTEL_AUDIO_CALL =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\"";
+ private static final String FEATURE_TAG_MMTEL_VIDEO_CALL =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\";video";
+
private static int sTestSlot = 0;
private static int sTestSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private static final Uri LISTENER_URI = Uri.withAppendedPath(Telephony.SimInfo.CONTENT_URI,
@@ -355,30 +368,131 @@
}
}
- // getUcePublishState
+ // Connect to the TestImsService
+ connectTestImsService();
+
+ // getUcePublishState without permission
try {
uceAdapter.getUcePublishState();
- fail("getUcePublishState should require READ_PRIVILEGED_PHONE_STATE.");
+ fail("getUcePublishState should require READ_PRIVILEGED_PHONE_STATE permission.");
} catch (SecurityException e) {
//expected
}
- // requestCapabilities
+ // getUcePublishState with permission
try {
- uceAdapter.requestCapabilities(numbers, Runnable::run,
- new RcsUceAdapter.CapabilitiesCallback() {
- @Override
- public void onCapabilitiesReceived(
- List<RcsContactUceCapability> capabilities) {}
- @Override
- public void onComplete() {}
- @Override
- public void onError(int errorCode, long retryAfterMilliseconds) {}
- });
- fail("requestCapabilities should require READ_PRIVILEGED_PHONE_STATE.");
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+ (m) -> m.getUcePublishState(), ImsException.class,
+ "android.permission.READ_PRIVILEGED_PHONE_STATE");
+ } catch (SecurityException e) {
+ fail("getUcePublishState should succeed with READ_PRIVILEGED_PHONE_STATE.");
+ } catch (ImsException e) {
+ // unsupported is a valid fail cause.
+ if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+ fail("getUcePublishState failed with code " + e.getCode());
+ }
+ }
+
+ final RcsUceAdapter.OnPublishStateChangedListener publishStateListener = (state) -> { };
+
+ // addOnPublishStateChangedListener without permission
+ try {
+ uceAdapter.addOnPublishStateChangedListener(Runnable::run, publishStateListener);
+ fail("addOnPublishStateChangedListener should require "
+ + "READ_PRIVILEGED_PHONE_STATE");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ // addOnPublishStateChangedListener with permission.
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+ (m) -> m.addOnPublishStateChangedListener(Runnable::run, publishStateListener),
+ ImsException.class,
+ "android.permission.READ_PRIVILEGED_PHONE_STATE");
+ } catch (SecurityException e) {
+ fail("addOnPublishStateChangedListener should succeed with "
+ + "READ_PRIVILEGED_PHONE_STATE.");
+ } catch (ImsException e) {
+ // unsupported is a valid fail cause.
+ if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+ fail("addOnPublishStateChangedListener failed with code " + e.getCode());
+ }
+ }
+
+ // removeOnPublishStateChangedListener without permission
+ try {
+ uceAdapter.removeOnPublishStateChangedListener(publishStateListener);
+ fail("removeOnPublishStateChangedListener should require "
+ + "READ_PRIVILEGED_PHONE_STATE");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ // Prepare the callback of the capability request
+ RcsUceAdapter.CapabilitiesCallback callback = new RcsUceAdapter.CapabilitiesCallback() {
+ @Override
+ public void onCapabilitiesReceived(List<RcsContactUceCapability> capabilities) {
+ }
+ @Override
+ public void onComplete() {
+ }
+ @Override
+ public void onError(int errorCode, long retryAfterMilliseconds) {
+ }
+ };
+
+ // requestCapabilities without permission
+ try {
+ uceAdapter.requestCapabilities(numbers, Runnable::run , callback);
+ fail("requestCapabilities should require ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
} catch (SecurityException e) {
//expected
}
+
+ // requestAvailability without permission
+ try {
+ uceAdapter.requestAvailability(sTestNumberUri, Runnable::run, callback);
+ fail("requestAvailability should require ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
+ } catch (SecurityException e) {
+ //expected
+ }
+
+ // Lunch an activity to stay in the foreground.
+ lunchUceActivity();
+
+ // requestCapabilities in the foreground
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+ (m) -> m.requestCapabilities(numbers, Runnable::run, callback),
+ ImsException.class,
+ "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+ } catch (SecurityException e) {
+ fail("requestCapabilities should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
+ } catch (ImsException e) {
+ // unsupported is a valid fail cause.
+ if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+ fail("requestCapabilities failed with code " + e.getCode());
+ }
+ }
+
+ // requestAvailability in the foreground
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+ (m) -> m.requestAvailability(sTestNumberUri, Runnable::run, callback),
+ ImsException.class,
+ "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+ } catch (SecurityException e) {
+ fail("requestAvailability should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
+ } catch (ImsException e) {
+ // unsupported is a valid fail cause.
+ if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+ fail("requestAvailability failed with code " + e.getCode());
+ }
+ }
+
+ // Finish the activity
+ finishUceActivity();
}
@Test
@@ -418,7 +532,7 @@
// The API requestCapabilities should fail when it doesn't grant the permission.
try {
uceAdapter.requestCapabilities(numbers, Runnable::run, callback);
- fail("requestCapabilities requires READ_PRIVILEGED_PHONE_STATE permission.");
+ fail("requestCapabilities requires ACCESS_USER_CAPABILITY_EXCHANGE permission.");
} catch (SecurityException e) {
//expected
}
@@ -426,7 +540,7 @@
// The API requestAvailability should fail when it doesn't grant the permission.
try {
uceAdapter.requestAvailability(sTestNumberUri, Runnable::run, callback);
- fail("requestAvailability requires READ_PRIVILEGED_PHONE_STATE permission.");
+ fail("requestAvailability requires ACCESS_USER_CAPABILITY_EXCHANGE permission.");
} catch (SecurityException e) {
//expected
}
@@ -439,21 +553,13 @@
// Connect to the TestImsService
connectTestImsService();
+ // Stay in the foreground.
+ lunchUceActivity();
+
TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
.getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
- // The API requestCapabilities is available to be called without exceptions.
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
- } catch (ImsException e) {
- fail("requestCapabilities failed " + e);
- }
+ requestCapabilities(uceAdapter, numbers, callback);
// Verify that the callback "onError" is called with the error code NOT_ENABLED because
// the carrier config KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is still false.
@@ -467,19 +573,7 @@
errorQueue.clear();
}
- // The API requestAvailability is available to be called without exceptions.
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- adapter -> adapter.requestAvailability(
- sTestNumberUri, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE.");
- } catch (ImsException e) {
- fail("requestAvailability failed " + e);
- }
+ requestAvailability(uceAdapter, sTestNumberUri, callback);
// Verify that the callback "onError" is called with the error code NOT_ENABLED because
// the carrier config KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is still false.
@@ -507,19 +601,7 @@
cb.onTerminated("", 0L);
});
- // Call the API requestCapabilities and it should work as expected after the
- // KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is updated to true.
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
- } catch (ImsException e) {
- fail("requestCapabilities failed " + e);
- }
+ requestCapabilities(uceAdapter, numbers, callback);
// Verify that the contact capability is received and the onCompleted is called.
RcsContactUceCapability capability = waitForResult(capabilityQueue);
@@ -531,25 +613,14 @@
capabilityQueue.clear();
removeTestContactFromEab();
- // Call the API requestAvailability and it should work as expected after the
- // KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is updated to true.
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
- } catch (ImsException e) {
- fail("requestAvailability failed " + e);
- }
+ requestAvailability(uceAdapter, sTestNumberUri, callback);
// Verify that the contact capability is received and the onCompleted is called.
capability = waitForResult(capabilityQueue);
verifyCapabilityResult(capability, sTestNumberUri, REQUEST_RESULT_FOUND, true, true);
waitForResult(completeQueue);
+ finishUceActivity();
overrideCarrierConfig(null);
}
@@ -563,7 +634,10 @@
assertNotNull("UCE adapter should not be null!", uceAdapter);
// Connect to the TestImsService
- setupTestImsService(uceAdapter);
+ setupTestImsService(uceAdapter, true, true, false);
+
+ // Stay in the foreground
+ lunchUceActivity();
List<Uri> contacts = Collections.singletonList(sTestNumberUri);
@@ -608,18 +682,7 @@
cb.onCommandError(cmdError);
});
- // Call the exposed API "requestCapabilities" to retrieve the contact's capabilities.
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
- } catch (ImsException e) {
- fail("requestCapabilities failed " + e);
- }
+ requestCapabilities(uceAdapter, contacts, callback);
// Verify that the callback "onError" is called with the expected error code.
try {
@@ -632,18 +695,7 @@
retryAfterQueue.clear();
}
- // Call another exposed API "requestAvailability" to retrieve the capabilities.
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- adapter -> adapter.requestAvailability(sTestNumberUri,
- Runnable::run, callback), ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
- } catch (ImsException e) {
- fail("requestAvailability failed " + e);
- }
+ requestAvailability(uceAdapter, sTestNumberUri, callback);
// Verify that the callback "onError" is called with the expected error code.
try {
@@ -657,6 +709,7 @@
}
});
+ finishUceActivity();
overrideCarrierConfig(null);
}
@@ -670,7 +723,7 @@
assertNotNull("UCE adapter should not be null!", uceAdapter);
// Connect to the ImsService
- setupTestImsService(uceAdapter);
+ setupTestImsService(uceAdapter, true, true /* presence cap */, false /* options */);
ArrayList<Uri> numbers = new ArrayList<>(1);
numbers.add(sTestNumberUri);
@@ -756,6 +809,9 @@
}
}, RcsUceAdapter.ERROR_SERVER_UNAVAILABLE);
+ // Stay in the foreground.
+ lunchUceActivity();
+
TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
.getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
@@ -765,19 +821,7 @@
cb.onNetworkResponse(networkResp.getKey(), networkResp.getValue());
});
- // Request capabilities by calling the API requestCapabilities
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
- } catch (ImsException e) {
- fail("requestCapabilities failed " + e);
- }
-
+ requestCapabilities(uceAdapter, numbers, callback);
// Verify that the callback "onError" is called with the expected error code.
try {
assertEquals(expectedCallbackResult.intValue(), waitForIntResult(errorQueue));
@@ -789,18 +833,7 @@
retryAfterQueue.clear();
}
- // Request capabilities by calling the API requestAvailability
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
- } catch (ImsException e) {
- fail("requestAvailability failed " + e);
- }
+ requestAvailability(uceAdapter, sTestNumberUri, callback);
// Verify that the callback "onError" is called with the expected error code.
try {
@@ -823,18 +856,7 @@
networkResp.getKey(), networkResp.getValue());
});
- // Request capabilities by calling the API requestCapabilities
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
- } catch (ImsException e) {
- fail("requestCapabilities failed " + e);
- }
+ requestCapabilities(uceAdapter, numbers, callback);
// Verify that the callback "onError" is called with the expected error code.
try {
@@ -847,18 +869,7 @@
retryAfterQueue.clear();
}
- // Request capabilities by calling the API requestAvailability
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
- } catch (ImsException e) {
- fail("requestAvailability failed " + e);
- }
+ requestAvailability(uceAdapter, sTestNumberUri, callback);
// Verify that the callback "onError" is called with the expected error code.
try {
@@ -879,18 +890,7 @@
cb.onNetworkResponse(networkResp, networkRespReason);
});
- // Request the capabilities by calling the API requestAvailability
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
- } catch (ImsException e) {
- fail("requestAvailability failed " + e);
- }
+ requestAvailability(uceAdapter, sTestNumberUri, callback);
// Verify that the callback "onError" is called with the error code FORBIDDEN
try {
@@ -903,18 +903,7 @@
retryAfterQueue.clear();
}
- // Request the capabilities again after the ImsService return the 403 error.
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE");
- } catch (ImsException e) {
- fail("requestCapabilities failed " + e);
- }
+ requestCapabilities(uceAdapter, numbers, callback);
// Verify that the capabilities request is sill failed because the ImsService has returned
// the 403 error before.
@@ -928,6 +917,7 @@
retryAfterQueue.clear();
}
+ finishUceActivity();
overrideCarrierConfig(null);
}
@@ -944,7 +934,7 @@
removeTestContactFromEab();
// Connect to the ImsService
- setupTestImsService(uceAdapter);
+ setupTestImsService(uceAdapter, true, true /* presence cap */, false /* OPTIONS */);
TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
.getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
@@ -995,18 +985,10 @@
cb.onTerminated("", 0L);
});
- // Request capabilities by calling the API requestCapabilities.
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
- } catch (ImsException e) {
- fail("requestCapabilities failed " + e);
- }
+ // Stay in the foreground.
+ lunchUceActivity();
+
+ requestCapabilities(uceAdapter, contacts, callback);
// Verify that all the three contact's capabilities are received
RcsContactUceCapability capability = waitForResult(capabilityQueue);
@@ -1042,18 +1024,7 @@
cb.onTerminated("", 0L);
});
- // Request capabilities by again calling the API requestCapabilities
- try {
- ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
- uceAdapter,
- adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
- ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
- } catch (SecurityException e) {
- fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
- } catch (ImsException e) {
- fail("requestCapabilities failed " + e);
- }
+ requestCapabilities(uceAdapter, contacts, callback);
// Verify the first contact is found.
capability = waitForResult(capabilityQueue);
@@ -1069,15 +1040,299 @@
// Verify the onCompleted is called
waitForResult(completeQueue);
+ finishUceActivity();
overrideCarrierConfig(null);
}
- private void setupTestImsService(RcsUceAdapter uceAdapter) throws Exception {
+ @Test
+ public void testRequestCapabilitiesWithOptionsMechanism() 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, false, true /* OPTIONS enabled */);
+
+ TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
+ .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
+
+ // The test contact
+ ArrayList<Uri> contacts = new ArrayList<>(3);
+ contacts.add(sTestNumberUri);
+
+ // The result callback
+ BlockingQueue<Long> errorQueue = new LinkedBlockingQueue<>();
+ BlockingQueue<Boolean> completeQueue = new LinkedBlockingQueue<>();
+ BlockingQueue<RcsContactUceCapability> capabilityQueue = new LinkedBlockingQueue<>();
+ RcsUceAdapter.CapabilitiesCallback callback = new RcsUceAdapter.CapabilitiesCallback() {
+ @Override
+ public void onCapabilitiesReceived(List<RcsContactUceCapability> capabilities) {
+ capabilities.forEach(c -> capabilityQueue.offer(c));
+ }
+ @Override
+ public void onComplete() {
+ completeQueue.offer(true);
+ }
+ @Override
+ public void onError(int errorCode, long retryAfterMilliseconds) {
+ errorQueue.offer(new Long(errorCode));
+ errorQueue.offer(retryAfterMilliseconds);
+ }
+ };
+
+ // Set the result of the network response is 200 OK.
+ final List<String> featureTags = new ArrayList<>();
+ featureTags.add(FEATURE_TAG_CHAT);
+ featureTags.add(FEATURE_TAG_FILE_TRANSFER);
+ featureTags.add(FEATURE_TAG_MMTEL_AUDIO_CALL);
+ featureTags.add(FEATURE_TAG_MMTEL_VIDEO_CALL);
+ capabilityExchangeImpl.setOptionsOperation((contact, myCapabilities, optionsCallback) -> {
+ int sipCode = 200;
+ String reason = "OK";
+ optionsCallback.onNetworkResponse(sipCode, reason, featureTags);
+ });
+
+ // Request capabilities by calling the API requestCapabilities.
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+ uceAdapter,
+ adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
+ ImsException.class,
+ "android.permission.READ_PRIVILEGED_PHONE_STATE");
+ } catch (SecurityException e) {
+ fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
+ } catch (ImsException e) {
+ fail("requestCapabilities failed " + e);
+ }
+
+ // Verify the callback "onCapabilitiesReceived" is called.
+ RcsContactUceCapability capability = waitForResult(capabilityQueue);
+ // Verify the callback "onComplete" is called.
+ waitForResult(completeQueue);
+ assertNotNull("RcsContactUceCapability should not be null", capability);
+ assertEquals(RcsContactUceCapability.SOURCE_TYPE_NETWORK, capability.getSourceType());
+ assertEquals(sTestNumberUri, capability.getContactUri());
+ assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND, capability.getRequestResult());
+ assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
+ capability.getCapabilityMechanism());
+ List<String> resultFeatureTags = capability.getOptionsFeatureTags();
+ assertEquals(featureTags.size(), resultFeatureTags.size());
+ for (String featureTag : featureTags) {
+ if (!resultFeatureTags.contains(featureTag)) {
+ fail("Cannot find feature tag in the result");
+ }
+ }
+ errorQueue.clear();
+ completeQueue.clear();
+ capabilityQueue.clear();
+ // Remove the test contact capabilities
+ removeTestContactFromEab();
+
+ // Request capabilities by calling the API requestAvailability.
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+ uceAdapter,
+ adapter -> adapter.requestAvailability(sTestContact2Uri,
+ Runnable::run, callback),
+ ImsException.class,
+ "android.permission.READ_PRIVILEGED_PHONE_STATE");
+ } catch (SecurityException e) {
+ fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
+ } catch (ImsException e) {
+ fail("requestCapabilities failed " + e);
+ }
+
+ // Verify the callback "onCapabilitiesReceived" is called.
+ capability = waitForResult(capabilityQueue);
+ // Verify the callback "onComplete" is called.
+ waitForResult(completeQueue);
+ assertNotNull("RcsContactUceCapability should not be null", capability);
+ assertEquals(RcsContactUceCapability.SOURCE_TYPE_NETWORK, capability.getSourceType());
+ assertEquals(sTestContact2Uri, capability.getContactUri());
+ assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND, capability.getRequestResult());
+ assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
+ capability.getCapabilityMechanism());
+ resultFeatureTags = capability.getOptionsFeatureTags();
+ assertEquals(featureTags.size(), resultFeatureTags.size());
+ for (String featureTag : featureTags) {
+ if (!resultFeatureTags.contains(featureTag)) {
+ fail("Cannot find feature tag in the result");
+ }
+ }
+ errorQueue.clear();
+ completeQueue.clear();
+ capabilityQueue.clear();
+ // Remove the test contact capabilities
+ removeTestContactFromEab();
+
+ // Set the OPTIONS result is failed.
+ capabilityExchangeImpl.setOptionsOperation((contact, myCapabilities, optionsCallback) -> {
+ int sipCode = 400;
+ String reason = "Bad Request";
+ optionsCallback.onNetworkResponse(sipCode, reason, Collections.EMPTY_LIST);
+ });
+
+ // Request capabilities by calling the API requestCapabilities.
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+ uceAdapter,
+ adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
+ ImsException.class,
+ "android.permission.READ_PRIVILEGED_PHONE_STATE");
+ } catch (SecurityException e) {
+ fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
+ } catch (ImsException e) {
+ fail("requestCapabilities failed " + e);
+ }
+
+ // Verify the callback "onError" is called.
+ assertEquals(RcsUceAdapter.ERROR_GENERIC_FAILURE, waitForLongResult(errorQueue));
+
+ // The callback "onCapabilitiesReceived" should not be called.
+ if (capabilityQueue.poll() != null) {
+ fail("onCapabilitiesReceived should not be called.");
+ }
+
+ overrideCarrierConfig(null);
+ }
+
+ @Test
+ public void testOptionsRequestFromNetwork() 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, false, true /* OPTIONS enabled */);
+
+ CapabilityExchangeEventListener eventListener =
+ sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+ final Uri contact = sTestContact2Uri;
+ List<String> remoteCapabilities = new ArrayList<>();
+ remoteCapabilities.add(FEATURE_TAG_CHAT);
+ remoteCapabilities.add(FEATURE_TAG_FILE_TRANSFER);
+ remoteCapabilities.add(FEATURE_TAG_MMTEL_AUDIO_CALL);
+ remoteCapabilities.add(FEATURE_TAG_MMTEL_VIDEO_CALL);
+ BlockingQueue<Pair<RcsContactUceCapability, Boolean>> respToCapRequestQueue =
+ new LinkedBlockingQueue<>();
+ OptionsRequestCallback callback = new OptionsRequestCallback() {
+ @Override
+ public void onRespondToCapabilityRequest(RcsContactUceCapability capabilities,
+ boolean isBlocked) {
+ respToCapRequestQueue.offer(new Pair<>(capabilities, isBlocked));
+ }
+ @Override
+ public void onRespondToCapabilityRequestWithError(int sipCode, String reason) {
+ }
+ };
+
+ // Notify the remote capability request
+ eventListener.onRemoteCapabilityRequest(contact, remoteCapabilities, callback);
+
+ // Verify receive the result
+ Pair<RcsContactUceCapability, Boolean> capability = waitForResult(respToCapRequestQueue);
+ assertNotNull("RcsContactUceCapability should not be null", capability);
+ assertEquals(RcsContactUceCapability.SOURCE_TYPE_NETWORK, capability.first.getSourceType());
+ assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND,
+ capability.first.getRequestResult());
+ assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
+ capability.first.getCapabilityMechanism());
+ // Should not report blocked
+ assertFalse("This number is not blocked, so the API should not report blocked",
+ capability.second);
+
+ overrideCarrierConfig(null);
+ }
+
+ @Test
+ public void testOptionsRequestFromNetworkBlocked() 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, false, true /* OPTIONS enabled */);
+
+ CapabilityExchangeEventListener eventListener =
+ sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+ final Uri contact = sTestNumberUri;
+ List<String> remoteCapabilities = new ArrayList<>();
+ remoteCapabilities.add(FEATURE_TAG_CHAT);
+ remoteCapabilities.add(FEATURE_TAG_FILE_TRANSFER);
+ remoteCapabilities.add(FEATURE_TAG_MMTEL_AUDIO_CALL);
+ remoteCapabilities.add(FEATURE_TAG_MMTEL_VIDEO_CALL);
+
+ BlockingQueue<Pair<RcsContactUceCapability, Boolean>> respToCapRequestQueue =
+ new LinkedBlockingQueue<>();
+ OptionsRequestCallback callback = new OptionsRequestCallback() {
+ @Override
+ public void onRespondToCapabilityRequest(RcsContactUceCapability capabilities,
+ boolean isBlocked) {
+ respToCapRequestQueue.offer(new Pair<>(capabilities, isBlocked));
+ }
+ @Override
+ public void onRespondToCapabilityRequestWithError(int sipCode, String reason) {
+ }
+ };
+
+ // Must be default SMS app to block numbers
+ sServiceConnector.setDefaultSmsApp();
+ Uri blockedUri = BlockedNumberUtil.insertBlockedNumber(getContext(), sTestPhoneNumber);
+ assertNotNull("could not block number", blockedUri);
+ try {
+ // Notify the remote capability request
+ eventListener.onRemoteCapabilityRequest(contact, remoteCapabilities, callback);
+
+ // Verify receive the result
+ Pair<RcsContactUceCapability, Boolean> capability =
+ waitForResult(respToCapRequestQueue);
+ assertNotNull("RcsContactUceCapability should not be null", capability);
+ assertEquals(RcsContactUceCapability.SOURCE_TYPE_NETWORK,
+ capability.first.getSourceType());
+ assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND,
+ capability.first.getRequestResult());
+ assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
+ capability.first.getCapabilityMechanism());
+ // Should report blocked
+ assertTrue("this number is blocked, so API should report blocked",
+ capability.second);
+ } finally {
+ BlockedNumberUtil.deleteBlockedNumber(getContext(), blockedUri);
+ sServiceConnector.restoreDefaultSmsApp();
+ }
+
+ overrideCarrierConfig(null);
+ }
+
+ private void setupTestImsService(RcsUceAdapter uceAdapter, boolean presencePublishEnabled,
+ boolean presenceCapExchangeEnabled, boolean sipOptionsEnabled) throws Exception {
// Trigger carrier config changed
PersistableBundle bundle = new PersistableBundle();
- bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, true);
+ bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+ presencePublishEnabled);
bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL,
- true);
+ presenceCapExchangeEnabled);
+ bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, sipOptionsEnabled);
overrideCarrierConfig(bundle);
// Enable the UCE setting.
@@ -1263,4 +1518,56 @@
Log.w("RcsUceAdapterTest", "Cannot remove test contacts from eab database: " + e);
}
}
+
+ private void requestCapabilities(RcsUceAdapter uceAdapter, List<Uri> numbers,
+ RcsUceAdapter.CapabilitiesCallback callback) {
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+ uceAdapter,
+ adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
+ ImsException.class,
+ "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+ } catch (SecurityException e) {
+ fail("requestCapabilities should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE. "
+ + "Exception: " + e);
+ } catch (ImsException e) {
+ fail("requestCapabilities failed " + e);
+ }
+ }
+
+ private void requestAvailability(RcsUceAdapter uceAdapter, Uri number,
+ RcsUceAdapter.CapabilitiesCallback callback) {
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+ uceAdapter,
+ adapter -> adapter.requestAvailability(number, Runnable::run, callback),
+ ImsException.class,
+ "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+ } catch (SecurityException e) {
+ fail("requestAvailability should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE. "
+ + "Exception: " + e);
+ } catch (ImsException e) {
+ fail("requestAvailability failed " + e);
+ }
+ }
+
+ private void lunchUceActivity() throws Exception {
+ final CountDownLatch countdownLatch = new CountDownLatch(1);
+ final Intent activityIntent = new Intent(getContext(), UceActivity.class);
+ activityIntent.setAction(Intent.ACTION_MAIN);
+ activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ UceActivity.setCountDownLatch(countdownLatch);
+ getContext().startActivity(activityIntent);
+ countdownLatch.await(5000, TimeUnit.MILLISECONDS);
+ }
+
+ private void finishUceActivity() {
+ final Intent finishIntent = new Intent(getContext(), UceActivity.class);
+ finishIntent.setAction(UceActivity.ACTION_FINISH);
+ finishIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ finishIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ finishIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ getContext().startActivity(finishIntent);
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
index d7906df..c8509b8 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
@@ -223,15 +223,38 @@
SipDelegateManager manager = getSipDelegateManager();
try {
manager.isSupported();
- fail("isSupported requires READ_PRIVILEGED_PHONE_STATE");
+ fail("isSupported requires READ_PRIVILEGED_PHONE_STATE or "
+ + "PERFORM_IMS_SINGLE_REGISTRATION");
} catch (SecurityException e) {
//expected
}
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+ manager, SipDelegateManager::isSupported, ImsException.class,
+ "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
+ } catch (ImsException e) {
+ // Not a problem, only checking permissions here.
+ } catch (SecurityException e) {
+ fail("isSupported requires READ_PRIVILEGED_PHONE_STATE or "
+ + "PERFORM_IMS_SINGLE_REGISTRATION, exception:" + e);
+ }
+ try {
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+ manager, SipDelegateManager::isSupported, ImsException.class,
+ "android.permission.READ_PRIVILEGED_PHONE_STATE");
+
+ } catch (ImsException e) {
+ // Not a problem, only checking permissions here.
+ } catch (SecurityException e) {
+ fail("isSupported requires READ_PRIVILEGED_PHONE_STATE or "
+ + "PERFORM_IMS_SINGLE_REGISTRATION, exception:" + e);
+ }
+
DelegateRequest d = new DelegateRequest(Collections.emptySet());
TestSipDelegateConnection c = new TestSipDelegateConnection(d);
try {
manager.createSipDelegate(d, Runnable::run, c, c);
- fail("createSipDelegate requires MODIFY_PHONE_STATE");
+ fail("createSipDelegate requires PERFORM_IMS_SINGLE_REGISTRATION");
} catch (SecurityException e) {
//expected
}
@@ -259,12 +282,12 @@
// return false.
Boolean result = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
manager, SipDelegateManager::isSupported, ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE");
+ "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
assertNotNull(result);
assertFalse("isSupported should return false on devices that do not "
+ "support feature FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION", result);
} catch (SecurityException e) {
- fail("isSupported requires READ_PRIVILEGED_PHONE_STATE permission");
+ fail("isSupported requires PERFORM_IMS_SINGLE_REGISTRATION permission");
}
try {
@@ -275,12 +298,12 @@
ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
manager, (m) -> m.createSipDelegate(request, Runnable::run,
delegateConn, delegateConn), ImsException.class,
- "android.permission.MODIFY_PHONE_STATE");
+ "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
fail("createSipDelegate should throw an ImsException with code "
+ "CODE_ERROR_UNSUPPORTED_OPERATION on devices that do not support feature "
+ "FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION");
} catch (SecurityException e) {
- fail("isSupported requires READ_PRIVILEGED_PHONE_STATE permission");
+ fail("isSupported requires PERFORM_IMS_SINGLE_REGISTRATION permission");
} catch (ImsException e) {
// expecting CODE_ERROR_UNSUPPORTED_OPERATION
if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
@@ -313,9 +336,9 @@
result = callUntilImsServiceIsAvailable(() ->
ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(manager,
SipDelegateManager::isSupported, ImsException.class,
- "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+ "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
} catch (SecurityException e) {
- fail("isSupported requires READ_PRIVILEGED_PHONE_STATE permission");
+ fail("isSupported requires PERFORM_IMS_SINGLE_REGISTRATION permission");
}
assertNotNull(result);
assertTrue("isSupported should return true", result);
@@ -335,7 +358,7 @@
Boolean result = callUntilImsServiceIsAvailable(() ->
ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
getSipDelegateManager(), SipDelegateManager::isSupported,
- ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+ ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
assertNotNull(result);
assertFalse("isSupported should return false if"
+ "CarrierConfigManager.Ims.KEY_RCS_SINGLE_REGISTRATION_REQUIRED_BOOL is set to "
@@ -365,7 +388,7 @@
Boolean result = callUntilImsServiceIsAvailable(() ->
ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
getSipDelegateManager(), SipDelegateManager::isSupported,
- ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+ ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
assertNotNull(result);
assertFalse("isSupported should return false in the case that the ImsService is only "
+ "attached for RCS and not MMTEL and RCS", result);
@@ -392,7 +415,7 @@
Boolean result = callUntilImsServiceIsAvailable(() ->
ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
getSipDelegateManager(), SipDelegateManager::isSupported,
- ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+ ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
assertNotNull(result);
assertFalse("isSupported should return false in the case that SipTransport is not "
+ "implemented", result);
@@ -417,7 +440,7 @@
Boolean result = callUntilImsServiceIsAvailable(() ->
ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
getSipDelegateManager(), SipDelegateManager::isSupported,
- ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+ ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
assertNotNull(result);
assertFalse("isSupported should return false in the case that SipTransport is not "
+ "set as capable in ImsService#getImsServiceCapabilities", result);
@@ -441,7 +464,7 @@
Boolean result = callUntilImsServiceIsAvailable(() ->
ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
getSipDelegateManager(), SipDelegateManager::isSupported,
- ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+ ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
assertNotNull(result);
assertFalse("isSupported should return false in the case that SipTransport is not "
+ "set as capable in ImsService#getImsServiceCapabilities", result);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
index 9cfbf18..99639a3 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
@@ -43,6 +43,12 @@
void execute(List<Uri> uris, SubscribeResponseCallback cb) throws ImsException;
}
+ @FunctionalInterface
+ public interface OptionsOperation {
+ void execute(Uri contactUri, List<String> myCapabilities, OptionsResponseCallback callback)
+ throws ImsException;
+ }
+
private DeviceCapPublishListener mPublishListener;
// The operation of publishing capabilities
@@ -51,6 +57,9 @@
// The operation of requesting capabilities.
private SubscribeOperation mSubscribeOperation;
+ // The operation of send Options
+ private OptionsOperation mOptionsOperation;
+
/**
* Create a new RcsCapabilityExchangeImplBase instance.
* @param executor The executor that remote calls from the framework will be called on.
@@ -68,6 +77,10 @@
mSubscribeOperation = operator;
}
+ public void setOptionsOperation(OptionsOperation operation) {
+ mOptionsOperation = operation;
+ }
+
@Override
public void publishCapabilities(String pidfXml, PublishResponseCallback cb) {
try {
@@ -85,4 +98,14 @@
Log.w(LOG_TAG, "subscribeForCapabilities exception: " + e);
}
}
+
+ @Override
+ public void sendOptionsCapabilityRequest(Uri contactUri, List<String> myCapabilities,
+ OptionsResponseCallback callback) {
+ try {
+ mOptionsOperation.execute(contactUri, myCapabilities, callback);
+ } catch (ImsException e) {
+ Log.w(LOG_TAG, "sendOptionsCapabilityRequest exception: " + e);
+ }
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
index ca7505f..1118009 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
@@ -80,20 +80,22 @@
ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
manager, (m) -> m.createSipDelegate(delegateRequest, Runnable::run, this,
this), ImsException.class,
- "android.permission.MODIFY_PHONE_STATE"));
+ "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
}
public void disconnect(SipDelegateManager manager, int reason) throws Exception {
ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
manager, (m) -> m.destroySipDelegate(connection, reason),
- ImsException.class, "android.permission.MODIFY_PHONE_STATE");
+ ImsException.class,
+ "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
}
public void triggerFullNetworkRegistration(SipDelegateManager manager, int sipCode,
String sipReason) throws Exception {
ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
manager, (m) -> m.triggerFullNetworkRegistration(connection, sipCode, sipReason),
- ImsException.class, "android.permission.MODIFY_PHONE_STATE");
+ ImsException.class,
+ "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
}
@Override
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/UceActivity.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/UceActivity.java
new file mode 100644
index 0000000..5b631ec
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/UceActivity.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.telephony.ims.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * The Activity to run the UCE APIs verification in the foreground.
+ */
+public class UceActivity extends Activity {
+
+ public static final String ACTION_FINISH = "android.telephony.ims.cts.action_finish";
+
+ private static CountDownLatch sCountDownLatch;
+
+ public static void setCountDownLatch(CountDownLatch countDownLatch) {
+ sCountDownLatch = countDownLatch;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ SurfaceView mSurfaceView = new SurfaceView(this);
+ mSurfaceView.setWillNotDraw(false);
+ mSurfaceView.setZOrderOnTop(true);
+ setContentView(mSurfaceView,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (sCountDownLatch != null) {
+ sCountDownLatch.countDown();
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ if (ACTION_FINISH.equals(intent.getAction())) {
+ finish();
+ sCountDownLatch = null;
+ }
+ }
+}
diff --git a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
index 2301438..c20165f 100644
--- a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
@@ -343,6 +343,68 @@
PollingCheck.waitFor(() -> !mActivity.isActivityTransitionRunning());
}
+ @Test
+ public void testTwiceForwardTwiceBack() throws Throwable {
+ enterScene(R.layout.scene1);
+ assertFalse(mActivity.isActivityTransitionRunning());
+
+ // A -> B
+ mActivityRule.runOnUiThread(() -> {
+ mActivity.getWindow().setExitTransition(new Fade());
+ Intent intent = new Intent(mActivity, TargetActivity.class);
+ intent.putExtra(TargetActivity.EXTRA_USE_ANIMATOR, true);
+ ActivityOptions activityOptions =
+ ActivityOptions.makeSceneTransitionAnimation(mActivity);
+ mActivity.startActivity(intent, activityOptions.toBundle());
+ });
+
+ assertTrue(mActivity.isActivityTransitionRunning());
+
+ TargetActivity targetActivity = waitForTargetActivity();
+ assertTrue(targetActivity.isActivityTransitionRunning());
+ mActivityRule.runOnUiThread(() -> { });
+ PollingCheck.waitFor(5000, () -> !targetActivity.isActivityTransitionRunning());
+
+ // B -> C
+ mActivityRule.runOnUiThread(() -> {
+ targetActivity.getWindow().setExitTransition(new Fade());
+ Intent intent = new Intent(targetActivity, TargetActivity.class);
+ intent.putExtra(TargetActivity.EXTRA_USE_ANIMATOR, true);
+ ActivityOptions activityOptions =
+ ActivityOptions.makeSceneTransitionAnimation(targetActivity);
+ targetActivity.startActivity(intent, activityOptions.toBundle());
+ });
+
+ assertTrue(targetActivity.isActivityTransitionRunning());
+
+ TargetActivity targetActivity2 = waitForTargetActivity2();
+ assertTrue(targetActivity2.isActivityTransitionRunning());
+ mActivityRule.runOnUiThread(() -> { });
+ PollingCheck.waitFor(5000, () -> !targetActivity2.isActivityTransitionRunning());
+
+ // C -> B
+ mActivityRule.runOnUiThread(() -> {
+ targetActivity2.finishAfterTransition();
+ // The target activity transition should start right away
+ assertTrue(targetActivity2.isActivityTransitionRunning());
+ });
+
+ // The source activity transition should start sometime later
+ PollingCheck.waitFor(() -> targetActivity.isActivityTransitionRunning());
+ PollingCheck.waitFor(() -> !targetActivity.isActivityTransitionRunning());
+
+ // B -> A
+ mActivityRule.runOnUiThread(() -> {
+ targetActivity.finishAfterTransition();
+ // The target activity transition should start right away
+ assertTrue(targetActivity.isActivityTransitionRunning());
+ });
+
+ // The source activity transition should start sometime later
+ PollingCheck.waitFor(() -> mActivity.isActivityTransitionRunning());
+ PollingCheck.waitFor(() -> !mActivity.isActivityTransitionRunning());
+ }
+
// Views that are excluded from the exit/enter transition shouldn't change visibility
@Test
public void untargetedViews() throws Throwable {
@@ -498,6 +560,23 @@
return activity[0];
}
+ private TargetActivity waitForTargetActivity2() throws Throwable {
+ verify(TargetActivity.sCreated, within(3000)).add(any());
+ TargetActivity[] activity = new TargetActivity[1];
+ mActivityRule.runOnUiThread(() -> {
+ assertEquals(2, TargetActivity.sCreated.size());
+ activity[0] = TargetActivity.sCreated.get(1);
+ });
+ assertTrue("There was no draw call", activity[0].drawnOnce.await(3, TimeUnit.SECONDS));
+ mActivityRule.runOnUiThread(() -> {
+ activity[0].getWindow().getDecorView().invalidate();
+ });
+ mActivityRule.runOnUiThread(() -> {
+ assertTrue(activity[0].preDrawCalls > 1);
+ });
+ return activity[0];
+ }
+
private Set<Integer> getTargetViewIds(TargetTracking transition) {
return transition.getTrackedTargets().stream()
.map(v -> v.getId())
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 3bb174c..7870e43 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
@@ -348,6 +348,31 @@
}
@Test
+ public void testFrontendToCiCam() throws Exception {
+ // tune to get frontend resource
+ 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);
+
+ if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+ // TODO: get real CiCam id from MediaCas
+ res = mTuner.connectFrontendToCiCam(0);
+ } else {
+ assertEquals(Tuner.INVALID_LTS_ID, mTuner.connectFrontendToCiCam(0));
+ }
+
+ if (res != Tuner.INVALID_LTS_ID) {
+ assertEquals(mTuner.disconnectFrontendToCiCam(0), Tuner.RESULT_SUCCESS);
+ } else {
+ // Make sure the connectFrontendToCiCam only fails because the current device
+ // does not support connecting frontend to cicam
+ assertEquals(mTuner.disconnectFrontendToCiCam(0), Tuner.RESULT_UNAVAILABLE);
+ }
+ }
+
+ @Test
public void testAvSyncId() throws Exception {
// open filter to get demux resource
Filter f = mTuner.openFilter(
@@ -376,9 +401,9 @@
assertNotNull(f);
assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
- assertNotEquals(Tuner.INVALID_FILTER_ID_64BIT, f.getId64Bit());
+ assertNotEquals(Tuner.INVALID_FILTER_ID_LONG, f.getIdLong());
} else {
- assertEquals(Tuner.INVALID_FILTER_ID_64BIT, f.getId64Bit());
+ assertEquals(Tuner.INVALID_FILTER_ID_LONG, f.getIdLong());
}
Settings settings = SectionSettingsWithTableInfo
@@ -527,12 +552,14 @@
public void testOpenDvrRecorder() throws Exception {
DvrRecorder d = mTuner.openDvrRecorder(100, getExecutor(), getRecordListener());
assertNotNull(d);
+ d.close();
}
@Test
public void testOpenDvPlayback() throws Exception {
DvrPlayback d = mTuner.openDvrPlayback(100, getExecutor(), getPlaybackListener());
assertNotNull(d);
+ d.close();
}
@Test
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
index 8f2c3a6..b92f079 100644
--- a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
@@ -18,12 +18,24 @@
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnControlPlaneIkeConfig;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager;
import android.os.ParcelUuid;
@@ -50,12 +62,14 @@
private final VcnManager mVcnManager;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyManager mTelephonyManager;
+ private final ConnectivityManager mConnectivityManager;
public VcnManagerTest() {
mContext = InstrumentationRegistry.getContext();
mVcnManager = mContext.getSystemService(VcnManager.class);
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
}
@Before
@@ -64,8 +78,43 @@
}
private VcnConfig buildVcnConfig() {
+ final IkeSaProposal ikeProposal =
+ new IkeSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+ .addDhGroup(DH_GROUP_2048_BIT_MODP)
+ .addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC)
+ .build();
+
+ final String serverHostname = "2001:db8:1::100";
+ final String testLocalId = "test.client.com";
+ final String testRemoteId = "test.server.com";
+ final byte[] psk = "psk".getBytes();
+
+ // TODO: b/180521384: Build the IkeSessionParams without a Context when the no-arg
+ // IkeSessionParams.Builder constructor is exposed.
+ final IkeSessionParams ikeParams =
+ new IkeSessionParams.Builder(mContext)
+ .setServerHostname(serverHostname)
+ .addSaProposal(ikeProposal)
+ .setLocalIdentification(new IkeFqdnIdentification(testLocalId))
+ .setRemoteIdentification(new IkeFqdnIdentification(testRemoteId))
+ .setAuthPsk(psk)
+ .build();
+
+ final ChildSaProposal childProposal =
+ new ChildSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128)
+ .build();
+ final TunnelModeChildSessionParams childParams =
+ new TunnelModeChildSessionParams.Builder().addSaProposal(childProposal).build();
+
+ final VcnControlPlaneIkeConfig controlConfig =
+ new VcnControlPlaneIkeConfig(ikeParams, childParams);
+
final VcnGatewayConnectionConfig gatewayConnConfig =
- new VcnGatewayConnectionConfig.Builder()
+ new VcnGatewayConnectionConfig.Builder(controlConfig)
.addExposedCapability(NET_CAPABILITY_INTERNET)
.addRequiredUnderlyingCapability(NET_CAPABILITY_INTERNET)
.setRetryInterval(
@@ -84,11 +133,23 @@
@Test(expected = SecurityException.class)
public void testSetVcnConfig_noCarrierPrivileges() throws Exception {
+ // TODO: b/180521384: Remove the assertion when constructing IkeSessionParams does not
+ // require an active default network.
+ assertNotNull(
+ "You must have an active network connection to complete CTS",
+ mConnectivityManager.getActiveNetwork());
+
mVcnManager.setVcnConfig(new ParcelUuid(UUID.randomUUID()), buildVcnConfig());
}
@Test
public void testSetVcnConfig_withCarrierPrivileges() throws Exception {
+ // TODO: b/180521384: Remove the assertion when constructing IkeSessionParams does not
+ // require an active default network.
+ assertNotNull(
+ "You must have an active network connection to complete CTS",
+ mConnectivityManager.getActiveNetwork());
+
final int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
CarrierPrivilegeUtils.withCarrierPrivileges(mContext, dataSubId, () -> {
SubscriptionGroupUtils.withEphemeralSubscriptionGroup(mContext, dataSubId, (subGrp) -> {
diff --git a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
index fbf9f9a..6206418 100644
--- a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
@@ -17,20 +17,21 @@
#include <android/choreographer.h>
#include <android/looper.h>
-
#include <jni.h>
#include <sys/time.h>
#include <time.h>
#include <chrono>
+#include <cmath>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <set>
#include <sstream>
+#include <string>
#include <thread>
-#define LOG_TAG "ChoreographerNative"
+#define LOG_TAG "ChoreographerNativeTest"
#define ASSERT(condition, format, args...) \
if (!(condition)) { \
@@ -45,18 +46,35 @@
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;
+ jmethodID checkRefreshRateIsCurrentAndSwitch;
+ } choreographerNativeTest;
+} gJni;
+
static std::mutex gLock;
static std::set<int64_t> gSupportedRefreshPeriods;
struct Callback {
Callback(const char* name): name(name) {}
- const char* name;
+ std::string name;
int count{0};
std::chrono::nanoseconds frameTime{0LL};
};
struct RefreshRateCallback {
RefreshRateCallback(const char* name): name(name) {}
- const char* name;
+ std::string name;
+ int count{0};
+ std::chrono::nanoseconds vsyncPeriod{0LL};
+};
+
+struct RefreshRateCallbackWithDisplayManager {
+ RefreshRateCallbackWithDisplayManager(const char* name, JNIEnv* env, jobject clazz)
+ : name(name), env(env), clazz(clazz) {}
+ std::string name;
+ JNIEnv* env;
+ jobject clazz;
int count{0};
std::chrono::nanoseconds vsyncPeriod{0LL};
};
@@ -79,6 +97,17 @@
cb->vsyncPeriod = std::chrono::nanoseconds{vsyncPeriodNanos};
}
+static void refreshRateCallbackWithDisplayManager(int64_t vsyncPeriodNanos, void* data) {
+ std::lock_guard<std::mutex> _l(gLock);
+ RefreshRateCallbackWithDisplayManager* cb =
+ static_cast<RefreshRateCallbackWithDisplayManager*>(data);
+ cb->count++;
+ cb->vsyncPeriod = std::chrono::nanoseconds{vsyncPeriodNanos};
+ cb->env->CallVoidMethod(cb->clazz,
+ gJni.choreographerNativeTest.checkRefreshRateIsCurrentAndSwitch,
+ static_cast<int>(std::round(1e9f / cb->vsyncPeriod.count())));
+}
+
static std::chrono::nanoseconds now() {
return std::chrono::steady_clock::now().time_since_epoch();
}
@@ -98,15 +127,15 @@
free(msg);
}
-static void verifyCallback(JNIEnv* env, Callback* cb, int expectedCount,
+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, expectedCount, cb->count);
+ 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;
+ auto duration = cb.frameTime - startTime;
ASSERT(duration < maxTime, "Callback '%s' has incorrect frame time in invocation %d",
- cb->name, expectedCount);
+ cb.name.c_str(), expectedCount);
}
}
@@ -120,25 +149,27 @@
return ss.str();
}
-static void verifyRefreshRateCallback(JNIEnv* env, RefreshRateCallback* cb, int expectedMin) {
+template <class T>
+static void verifyRefreshRateCallback(JNIEnv* env, const T& cb, int expectedMin) {
std::lock_guard<std::mutex> _l(gLock);
- ASSERT(cb->count >= expectedMin, "Choreographer failed to invoke '%s' %d times - actual: %d",
- cb->name, expectedMin, cb->count);
+ ASSERT(cb.count >= expectedMin, "Choreographer failed to invoke '%s' %d times - actual: %d",
+ cb.name.c_str(), expectedMin, cb.count);
// Unfortunately we can't verify the specific vsync period as public apis
// don't provide a guarantee that we adhere to a particular refresh rate.
// The best we can do is check that the reported period is contained in the
- // set of suppoted periods.
- ASSERT(cb->vsyncPeriod > ZERO,
- "Choreographer failed to report a nonzero refresh period invoking '%s'",
- cb->name);
- ASSERT(gSupportedRefreshPeriods.count(cb->vsyncPeriod.count()) > 0,
- "Choreographer failed to report a supported refresh period invoking '%s': supported periods: %s, actual: %lu",
- cb->name, dumpSupportedRefreshPeriods().c_str(), cb->vsyncPeriod.count());
+ // set of supported periods.
+ ASSERT(cb.vsyncPeriod > ZERO,
+ "Choreographer failed to report a nonzero refresh period invoking '%s'",
+ cb.name.c_str());
+ ASSERT(gSupportedRefreshPeriods.count(cb.vsyncPeriod.count()) > 0,
+ "Choreographer failed to report a supported refresh period invoking '%s': supported "
+ "periods: %s, actual: %lu",
+ cb.name.c_str(), dumpSupportedRefreshPeriods().c_str(), cb.vsyncPeriod.count());
}
-static void resetRefreshRateCallback(RefreshRateCallback* cb) {
+static void resetRefreshRateCallback(RefreshRateCallback& cb) {
std::lock_guard<std::mutex> _l(gLock);
- cb->count = 0;
+ cb.count = 0;
}
static jlong android_view_cts_ChoreographerNativeTest_getChoreographer(JNIEnv*, jclass) {
@@ -162,24 +193,24 @@
static void android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- Callback* cb1 = new Callback("cb1");
- Callback* cb2 = new Callback("cb2");
+ Callback cb1("cb1");
+ Callback cb2("cb2");
auto start = now();
- AChoreographer_postFrameCallback64(choreographer, frameCallback64, cb1);
- AChoreographer_postFrameCallback64(choreographer, frameCallback64, cb2);
+ AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb1);
+ AChoreographer_postFrameCallback64(choreographer, frameCallback64, &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;
+ 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_postFrameCallback64(choreographer, frameCallback64, cb1);
+ AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb1);
start = now();
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
verifyCallback(env, cb1, 2, start, NOMINAL_VSYNC_PERIOD * 3);
@@ -189,11 +220,11 @@
static void android_view_cts_ChoreographerNativeTest_testPostCallback64WithDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- Callback* cb1 = new Callback("cb1");
+ Callback cb1 = Callback("cb1");
auto start = now();
auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
- AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, cb1, delay);
+ AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, &cb1, delay);
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
verifyCallback(env, cb1, 0, start, ZERO);
@@ -205,16 +236,16 @@
static void android_view_cts_ChoreographerNativeTest_testPostCallbackWithoutDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- Callback* cb1 = new Callback("cb1");
- Callback* cb2 = new Callback("cb2");
+ Callback cb1("cb1");
+ Callback cb2("cb2");
auto start = now();
const auto delay = NOMINAL_VSYNC_PERIOD * 3;
// Delay calculations are known to be broken on 32-bit systems (overflow),
// so we skip testing the delay on such systems by setting this to ZERO.
const auto delayToTest = sizeof(long) == sizeof(int64_t) ? delay : ZERO;
- AChoreographer_postFrameCallback(choreographer, frameCallback, cb1);
- AChoreographer_postFrameCallback(choreographer, frameCallback, cb2);
+ AChoreographer_postFrameCallback(choreographer, frameCallback, &cb1);
+ AChoreographer_postFrameCallback(choreographer, frameCallback, &cb2);
std::this_thread::sleep_for(delay);
verifyCallback(env, cb1, 1, start, delayToTest);
@@ -224,12 +255,12 @@
// part of the test on systems known to be broken.
if (sizeof(long) == sizeof(int64_t)) {
std::lock_guard<std::mutex> _l{gLock};
- auto delta = cb2->frameTime - cb1->frameTime;
+ 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_postFrameCallback(choreographer, frameCallback, cb1);
+ AChoreographer_postFrameCallback(choreographer, frameCallback, &cb1);
start = now();
std::this_thread::sleep_for(delay);
@@ -245,11 +276,11 @@
}
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- Callback* cb1 = new Callback("cb1");
+ Callback cb1("cb1");
auto start = now();
auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
- AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, cb1, delay);
+ AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, &cb1, delay);
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
verifyCallback(env, cb1, 0, start, ZERO);
@@ -263,12 +294,12 @@
static void android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithoutDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- Callback* cb1 = new Callback("cb1");
- Callback* cb64 = new Callback("cb64");
+ Callback cb1("cb1");
+ Callback cb64("cb64");
auto start = now();
- AChoreographer_postFrameCallback(choreographer, frameCallback, cb1);
- AChoreographer_postFrameCallback64(choreographer, frameCallback64, cb64);
+ AChoreographer_postFrameCallback(choreographer, frameCallback, &cb1);
+ AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb64);
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
verifyCallback(env, cb1, 1, start, ZERO);
@@ -278,12 +309,12 @@
// part of the test on systems known to be broken.
if (sizeof(long) == sizeof(int64_t)) {
std::lock_guard<std::mutex> _l{gLock};
- auto delta = cb64->frameTime - cb1->frameTime;
+ auto delta = cb64.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_postFrameCallback64(choreographer, frameCallback64, cb64);
+ AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb64);
start = now();
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
verifyCallback(env, cb1, 1, start, ZERO);
@@ -293,13 +324,13 @@
static void android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- Callback* cb1 = new Callback("cb1");
- Callback* cb64 = new Callback("cb64");
+ Callback cb1("cb1");
+ Callback cb64("cb64");
auto start = now();
auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
- AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, cb1, delay);
- AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, cb64, delay);
+ AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, &cb1, delay);
+ AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, &cb64, delay);
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
verifyCallback(env, cb1, 0, start, ZERO);
@@ -315,84 +346,84 @@
static void android_view_cts_ChoreographerNativeTest_testRefreshRateCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- RefreshRateCallback* cb = new RefreshRateCallback("cb");
+ RefreshRateCallback cb("cb");
- AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb);
+ AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb);
// Give the display system time to push an initial callback.
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
- verifyRefreshRateCallback(env, cb, 1);
- AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb);
+ verifyRefreshRateCallback<RefreshRateCallback>(env, cb, 1);
+ AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb);
}
static void android_view_cts_ChoreographerNativeTest_testUnregisteringRefreshRateCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- RefreshRateCallback* cb1 = new RefreshRateCallback("cb1");
- RefreshRateCallback* cb2 = new RefreshRateCallback("cb2");
+ RefreshRateCallback cb1("cb1");
+ RefreshRateCallback cb2("cb2");
- AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
+ AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
// Give the display system time to push an initial callback.
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
- verifyRefreshRateCallback(env, cb1, 1);
+ verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 1);
- AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb1);
+ AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
// Flush out pending callback events for the callback
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
resetRefreshRateCallback(cb1);
- AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+ AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
// Verify that cb2 is called on registration, but not cb1.
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
- verifyRefreshRateCallback(env, cb1, 0);
- verifyRefreshRateCallback(env, cb2, 1);
- AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+ verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 0);
+ verifyRefreshRateCallback<RefreshRateCallback>(env, cb2, 1);
+ AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
}
static void android_view_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- RefreshRateCallback* cb1 = new RefreshRateCallback("cb1");
- RefreshRateCallback* cb2 = new RefreshRateCallback("cb2");
+ RefreshRateCallback cb1("cb1");
+ RefreshRateCallback cb2("cb2");
- AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
- AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+ AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
+ AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
// Give the display system time to push an initial refresh rate change.
// Polling the event will allow both callbacks to be triggered.
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
- verifyRefreshRateCallback(env, cb1, 1);
- verifyRefreshRateCallback(env, cb2, 1);
+ verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 1);
+ verifyRefreshRateCallback<RefreshRateCallback>(env, cb2, 1);
- AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb1);
- AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+ AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
+ AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
}
static void android_view_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- RefreshRateCallback* cb1 = new RefreshRateCallback("cb1");
- RefreshRateCallback* cb2 = new RefreshRateCallback("cb2");
+ RefreshRateCallback cb1("cb1");
+ RefreshRateCallback cb2("cb2");
- AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
- AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb1);
+ AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
+ AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
// Give the display system time to push an initial callback.
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
- verifyRefreshRateCallback(env, cb1, 1);
+ verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 1);
- AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb1);
+ AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb1);
// Flush out pending callback events for the callback
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
resetRefreshRateCallback(cb1);
- AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+ AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
// Verify that cb1 is not called again, even thiough it was registered once
// and unregistered again
std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10);
- verifyRefreshRateCallback(env, cb1, 0);
- AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb2);
+ verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 0);
+ AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb2);
}
// This test must be run on the UI thread for fine-grained control of looper
@@ -400,27 +431,27 @@
static void android_view_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
- RefreshRateCallback* cb = new RefreshRateCallback("cb");
+ RefreshRateCallback cb("cb");
- AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb);
+ AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb);
- Callback* cb1 = new Callback("cb1");
- Callback* cb64 = new Callback("cb64");
+ Callback cb1("cb1");
+ Callback cb64("cb64");
auto start = now();
auto vsyncPeriod = std::chrono::duration_cast<std::chrono::milliseconds>(
NOMINAL_VSYNC_PERIOD)
.count();
auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
- AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, cb1, delay);
- AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, cb64, delay);
+ AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, &cb1, delay);
+ AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, &cb64, delay);
std::this_thread::sleep_for(DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 10);
// Ensure that callbacks are seen by the looper instance at approximately
// the same time, and provide enough time for the looper instance to process
// the delayed callback and the requested vsync signal if needed.
ALooper_pollAll(vsyncPeriod * 5, nullptr, nullptr, nullptr);
- verifyRefreshRateCallback(env, cb, 1);
+ verifyRefreshRateCallback<RefreshRateCallback>(env, cb, 1);
verifyCallback(env, cb64, 1, start,
DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 15);
const auto delayToTestFor32Bit =
@@ -428,41 +459,89 @@
? DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 15
: ZERO;
verifyCallback(env, cb1, 1, start, delayToTestFor32Bit);
- AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb);
+ AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb);
+}
+
+// This test cannot be run on the UI thread because it relies on callbacks to be dispatched on the
+// application UI thread.
+static void
+android_view_cts_ChoreographerNativeTest_testRefreshRateCallbacksAreSyncedWithDisplayManager(
+ JNIEnv* env, jobject clazz) {
+ // Test harness choreographer is not on the main thread, so create a thread-local choreographer
+ // instance.
+ ALooper_prepare(0);
+ AChoreographer* choreographer = AChoreographer_getInstance();
+ RefreshRateCallbackWithDisplayManager cb("cb", env, clazz);
+
+ AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallbackWithDisplayManager,
+ &cb);
+
+ auto delayPeriod = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
+
+ const size_t numRuns = 1000;
+ int previousCount = 0;
+ for (int i = 0; i < numRuns; ++i) {
+ const size_t numTries = 5;
+ for (int j = 0; j < numTries; j++) {
+ // In theory we only need to poll once because the test harness configuration should
+ // enforce that we won't get spurious callbacks. In practice, there may still be
+ // spurious callbacks due to hotplug or other display events that aren't suppressed. So
+ // we add some slack by retrying a few times, but we stop at the first refresh rate
+ // callback (1) to keep the test runtime reasonably short, and (2) to keep the test
+ // under better control so that it does not spam the system with refresh rate changes.
+ int result = ALooper_pollOnce(delayPeriod * 5, nullptr, nullptr, nullptr);
+ ASSERT(result == ALOOPER_POLL_CALLBACK, "Callback failed on run: %d with error: %d", i,
+ result);
+ if (previousCount != cb.count) {
+ verifyRefreshRateCallback<RefreshRateCallbackWithDisplayManager>(env, cb,
+ previousCount + 1);
+ previousCount = cb.count;
+ break;
+ }
+
+ ASSERT(j < numTries - 1, "No callback observed for run: %d", i);
+ }
+ }
+ AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb);
}
static JNINativeMethod gMethods[] = {
- { "nativeGetChoreographer", "()J",
- (void *) android_view_cts_ChoreographerNativeTest_getChoreographer},
- { "nativePrepareChoreographerTests", "(J[J)Z",
- (void *) android_view_cts_ChoreographerNativeTest_prepareChoreographerTests},
- { "nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback},
- { "nativeTestPostCallback64WithDelayEventuallyRunsCallbacks", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testPostCallback64WithDelayEventuallyRunsCallback},
- { "nativeTestPostCallbackWithoutDelayEventuallyRunsCallbacks", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testPostCallbackWithoutDelayEventuallyRunsCallback},
- { "nativeTestPostCallbackWithDelayEventuallyRunsCallbacks", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testPostCallbackWithDelayEventuallyRunsCallback},
- { "nativeTestPostCallbackMixedWithoutDelayEventuallyRunsCallbacks", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithoutDelayEventuallyRunsCallback},
- { "nativeTestPostCallbackMixedWithDelayEventuallyRunsCallbacks", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithDelayEventuallyRunsCallback},
- { "nativeTestRefreshRateCallback", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testRefreshRateCallback},
- { "nativeTestUnregisteringRefreshRateCallback", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testUnregisteringRefreshRateCallback},
- { "nativeTestMultipleRefreshRateCallbacks", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks},
- { "nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice},
- { "nativeTestRefreshRateCallbackMixedWithFrameCallbacks", "(J)V",
- (void *) android_view_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks},
+ {"nativeGetChoreographer", "()J",
+ (void*)android_view_cts_ChoreographerNativeTest_getChoreographer},
+ {"nativePrepareChoreographerTests", "(J[J)Z",
+ (void*)android_view_cts_ChoreographerNativeTest_prepareChoreographerTests},
+ {"nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback},
+ {"nativeTestPostCallback64WithDelayEventuallyRunsCallbacks", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testPostCallback64WithDelayEventuallyRunsCallback},
+ {"nativeTestPostCallbackWithoutDelayEventuallyRunsCallbacks", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testPostCallbackWithoutDelayEventuallyRunsCallback},
+ {"nativeTestPostCallbackWithDelayEventuallyRunsCallbacks", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testPostCallbackWithDelayEventuallyRunsCallback},
+ {"nativeTestPostCallbackMixedWithoutDelayEventuallyRunsCallbacks", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithoutDelayEventuallyRunsCallback},
+ {"nativeTestPostCallbackMixedWithDelayEventuallyRunsCallbacks", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithDelayEventuallyRunsCallback},
+ {"nativeTestRefreshRateCallback", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testRefreshRateCallback},
+ {"nativeTestUnregisteringRefreshRateCallback", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testUnregisteringRefreshRateCallback},
+ {"nativeTestMultipleRefreshRateCallbacks", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks},
+ {"nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice},
+ {"nativeTestRefreshRateCallbackMixedWithFrameCallbacks", "(J)V",
+ (void*)android_view_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks},
+ {"nativeTestRefreshRateCallbacksAreSyncedWithDisplayManager", "()V",
+ (void*)android_view_cts_ChoreographerNativeTest_testRefreshRateCallbacksAreSyncedWithDisplayManager},
};
int register_android_view_cts_ChoreographerNativeTest(JNIEnv* env)
{
jclass clazz = env->FindClass("android/view/cts/ChoreographerNativeTest");
+ gJni.choreographerNativeTest.clazz = static_cast<jclass>(env->NewGlobalRef(clazz));
+ gJni.choreographerNativeTest.checkRefreshRateIsCurrentAndSwitch =
+ env->GetMethodID(clazz, "checkRefreshRateIsCurrentAndSwitch", "(I)V");
return env->RegisterNatives(clazz, gMethods,
sizeof(gMethods) / sizeof(JNINativeMethod));
}
diff --git a/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java b/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
index a8e0134..4fd3f0f 100644
--- a/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
+++ b/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
@@ -16,21 +16,32 @@
package android.view.cts;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.Manifest;
import android.content.Context;
import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
import android.view.Display;
+import android.view.Display.Mode;
+import android.view.Window;
+import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,6 +54,18 @@
public class ChoreographerNativeTest {
private long mChoreographerPtr;
+ @Rule
+ public ActivityTestRule<CtsActivity> mTestActivityRule =
+ new ActivityTestRule<>(
+ CtsActivity.class);
+
+ @Rule
+ public final AdoptShellPermissionsRule mShellPermissionsRule =
+ new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
+ Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE);
+
private static native long nativeGetChoreographer();
private static native boolean nativePrepareChoreographerTests(long ptr, long[] refreshPeriods);
private static native void nativeTestPostCallbackWithoutDelayEventuallyRunsCallbacks(long ptr);
@@ -61,9 +84,12 @@
private static native void nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice(
long ptr);
private static native void nativeTestRefreshRateCallbackMixedWithFrameCallbacks(long ptr);
+ private native void nativeTestRefreshRateCallbacksAreSyncedWithDisplayManager();
private Context mContext;
private DisplayManager mDisplayManager;
+ private Display mDefaultDisplay;
+ private long[] mSupportedPeriods;
static {
System.loadLibrary("ctsview_jni");
@@ -80,14 +106,14 @@
.findFirst();
assertTrue(defaultDisplayOpt.isPresent());
- Display defaultDisplay = defaultDisplayOpt.get();
+ mDefaultDisplay = defaultDisplayOpt.get();
- long[] supportedPeriods = Arrays.stream(defaultDisplay.getSupportedModes())
+ mSupportedPeriods = Arrays.stream(mDefaultDisplay.getSupportedModes())
.mapToLong(mode -> (long) (Duration.ofSeconds(1).toNanos() / mode.getRefreshRate()))
.toArray();
mChoreographerPtr = nativeGetChoreographer();
- if (!nativePrepareChoreographerTests(mChoreographerPtr, supportedPeriods)) {
+ if (!nativePrepareChoreographerTests(mChoreographerPtr, mSupportedPeriods)) {
fail("Failed to setup choreographer tests");
}
}
@@ -159,4 +185,57 @@
nativeTestRefreshRateCallbackMixedWithFrameCallbacks(mChoreographerPtr);
}
+ @SmallTest
+ @Test
+ public void testRefreshRateCallbacksIsSyncedWithDisplayManager() {
+ if (mSupportedPeriods.length <= 1) {
+ return;
+ }
+ int initialMatchContentFrameRate = 0;
+ try {
+
+ // Set-up just for this particular test:
+ // We must force the screen to be on for this window, and DisplayManager must be
+ // configured to always respect the app-requested refresh rate.
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(() -> {
+ mTestActivityRule.getActivity().getWindow().addFlags(
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ });
+ mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+ initialMatchContentFrameRate = mDisplayManager.getRefreshRateSwitchingType();
+ mDisplayManager.setRefreshRateSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
+ nativeTestRefreshRateCallbacksAreSyncedWithDisplayManager();
+ } finally {
+ mDisplayManager.setRefreshRateSwitchingType(initialMatchContentFrameRate);
+ mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+ }
+ }
+
+ // Called by jni in a refresh rate callback
+ private void checkRefreshRateIsCurrentAndSwitch(int refreshRate) {
+ assertEquals(Math.round(mDefaultDisplay.getRefreshRate()), refreshRate);
+
+ Optional<Mode> nextMode = Arrays.stream(mDefaultDisplay.getSupportedModes())
+ .sorted((left, right) ->
+ Float.compare(right.getRefreshRate(), left.getRefreshRate()))
+ .filter(mode -> Math.round(mode.getRefreshRate()) != refreshRate)
+ .findFirst();
+
+ assertTrue(nextMode.isPresent());
+
+ Mode mode = nextMode.get();
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(() -> {
+ Window window = mTestActivityRule.getActivity().getWindow();
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.preferredDisplayModeId = mode.getModeId();
+ window.setAttributes(params);
+ });
+ }
+
+
+
}
diff --git a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
index 1d741aa..6ee1441 100644
--- a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
+++ b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
@@ -175,27 +175,42 @@
}
private void callGetMetric(FrameMetrics frameMetrics) {
- // The return values for non-boolean metrics do not have expected values. Here we
- // are verifying that calling getMetrics does not crash
- frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION);
- frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION);
- frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION);
- frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
- frameMetrics.getMetric(FrameMetrics.DRAW_DURATION);
- frameMetrics.getMetric(FrameMetrics.SYNC_DURATION);
- frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION);
- frameMetrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION);
- frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
// Perform basic checks on timestamp values.
+ long unknownDelay = frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION);
+ long input = frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION);
+ long animation = frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION);
+ long layoutMeasure = frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
+ long draw = frameMetrics.getMetric(FrameMetrics.DRAW_DURATION);
+ long sync = frameMetrics.getMetric(FrameMetrics.SYNC_DURATION);
+ long commandIssue = frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION);
+ long swapBuffers = frameMetrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION);
+ long gpuDuration = frameMetrics.getMetric(FrameMetrics.GPU_DURATION);
+ long deadline = frameMetrics.getMetric(FrameMetrics.DEADLINE);
+ long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
long intended_vsync = frameMetrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP);
long vsync = frameMetrics.getMetric(FrameMetrics.VSYNC_TIMESTAMP);
- long now = System.nanoTime();
+
+ assertTrue(unknownDelay > 0);
+ assertTrue(input > 0);
+ assertTrue(animation > 0);
+ assertTrue(layoutMeasure > 0);
+ assertTrue(draw > 0);
+ assertTrue(sync > 0);
+ assertTrue(commandIssue > 0);
+ assertTrue(swapBuffers > 0);
assertTrue(intended_vsync > 0);
assertTrue(vsync > 0);
+ assertTrue(gpuDuration > 0);
+ assertTrue(totalDuration > 0);
+ assertTrue(deadline > 0);
+
+ long now = System.nanoTime();
assertTrue(intended_vsync < now);
assertTrue(vsync < now);
assertTrue(vsync >= intended_vsync);
+ assertTrue(totalDuration >= unknownDelay + input + animation + layoutMeasure + draw + sync
+ + commandIssue + swapBuffers + gpuDuration);
// This is the only boolean metric so far
final long firstDrawFrameMetric = frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME);
diff --git a/tests/tests/voiceRecognition/AndroidManifest.xml b/tests/tests/voiceRecognition/AndroidManifest.xml
index 41e29cb..1370eb1 100644
--- a/tests/tests/voiceRecognition/AndroidManifest.xml
+++ b/tests/tests/voiceRecognition/AndroidManifest.xml
@@ -28,6 +28,15 @@
android:label="SpeechRecognitionActivity"
android:exported="true">
</activity>
+
+ <service android:name="CtsRecognitionService"
+ android:label="@string/service_name"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.speech.RecognitionService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java b/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java
index d0590a4..cda65f00 100644
--- a/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java
+++ b/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java
@@ -56,7 +56,9 @@
// empty bundle here.
try {
listener.results(Bundle.EMPTY);
+ Log.i(TAG, "Invoked #results");
} catch (RemoteException e) {
+ Log.e(TAG, "Failed to invoke #results", e);
}
}
mediaRecorderReady();
diff --git a/tests/tests/voiceRecognition/res/values/strings.xml b/tests/tests/voiceRecognition/res/values/strings.xml
new file mode 100644
index 0000000..6a1c7da
--- /dev/null
+++ b/tests/tests/voiceRecognition/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="service_name">CtsRecognitionService</string>
+</resources>
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/AbstractRecognitionServiceTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/AbstractRecognitionServiceTest.java
new file mode 100644
index 0000000..6f89c28
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/AbstractRecognitionServiceTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicerecognition.cts;
+
+import static android.voicerecognition.cts.CallbackMethod.CALLBACK_METHOD_ERROR;
+import static android.voicerecognition.cts.CallbackMethod.CALLBACK_METHOD_RESULTS;
+import static android.voicerecognition.cts.CallbackMethod.CALLBACK_METHOD_UNSPECIFIED;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_CANCEL;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_DESTROY;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_START_LISTENING;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_STOP_LISTENING;
+import static android.voicerecognition.cts.TestObjects.START_LISTENING_INTENT;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/** Abstract implementation for {@link android.speech.SpeechRecognizer} CTS tests. */
+abstract class AbstractRecognitionServiceTest {
+ private static final String TAG = AbstractRecognitionServiceTest.class.getSimpleName();
+
+ private static final long INDICATOR_DISMISS_TIMEOUT = 5000L;
+ private static final long WAIT_TIMEOUT_MS = 30000L; // 30 secs
+ private static final long SEQUENCE_TEST_WAIT_TIMEOUT_MS = 5000L;
+
+ private final String CTS_VOICE_RECOGNITION_SERVICE =
+ "android.recognitionservice.service/android.recognitionservice.service"
+ + ".CtsVoiceRecognitionService";
+
+ private final String IN_PACKAGE_RECOGNITION_SERVICE =
+ "android.voicerecognition.cts/android.voicerecognition.cts.CtsRecognitionService";
+
+ @Rule
+ public ActivityTestRule<SpeechRecognitionActivity> mActivityTestRule =
+ new ActivityTestRule<>(SpeechRecognitionActivity.class);
+
+ private UiDevice mUiDevice;
+ private SpeechRecognitionActivity mActivity;
+
+ abstract void setCurrentRecognizer(String recognizer);
+
+ abstract boolean isOnDeviceTest();
+
+ @Nullable
+ abstract String customRecognizer();
+
+ @Before
+ public void setup() {
+ prepareDevice();
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mActivity = mActivityTestRule.getActivity();
+ mActivity.init(isOnDeviceTest(), customRecognizer());
+ }
+
+ @Test
+ public void testStartListening() throws Throwable {
+ setCurrentRecognizer(CTS_VOICE_RECOGNITION_SERVICE);
+ mUiDevice.waitForIdle();
+
+ mActivity.startListening();
+ try {
+ // startListening() will call noteProxyOpNoTrow(), if the permission check pass then the
+ // RecognitionService.onStartListening() will be called. Otherwise, a TimeoutException
+ // will be thrown.
+ assertThat(mActivity.mCountDownLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ assertWithMessage("onStartListening() not called. " + e).fail();
+ }
+ // Wait for the privacy indicator to disappear to avoid the test becoming flaky.
+ SystemClock.sleep(INDICATOR_DISMISS_TIMEOUT);
+ }
+
+ @Test
+ public void sequenceTest_startListening_stopListening_results() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_STOP_LISTENING),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_UNSPECIFIED,
+ CALLBACK_METHOD_RESULTS),
+ /* expected service methods propagated: */ ImmutableList.of(true, true),
+ /* expected callback methods invoked: */ ImmutableList.of(
+ CALLBACK_METHOD_RESULTS)
+ );
+ }
+
+ /** Tests that stopListening() is ignored after results(). */
+ @Test
+ public void sequenceTest_startListening_results_stopListening() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_STOP_LISTENING),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_RESULTS),
+ /* expected service methods propagated: */ ImmutableList.of(true, false),
+ /* expected callback methods invoked: */ ImmutableList.of(
+ CALLBACK_METHOD_RESULTS,
+ CALLBACK_METHOD_ERROR)
+ );
+ }
+
+ /** Tests that cancel() is ignored after results(). */
+ @Test
+ public void sequenceTest_startListening_results_cancel() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_CANCEL),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_RESULTS),
+ /* expected service methods propagated: */ ImmutableList.of(true, false),
+ /* expected callback methods invoked: */ ImmutableList.of(
+ CALLBACK_METHOD_RESULTS)
+ );
+ }
+
+ /** Tests that we can kick off execution again after results(). */
+ @Test
+ public void sequenceTest_startListening_results_startListening_results() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_START_LISTENING),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_RESULTS,
+ CALLBACK_METHOD_RESULTS),
+ /* expected service methods propagated: */ ImmutableList.of(true, true),
+ /* expected callback methods invoked: */ ImmutableList.of(
+ CALLBACK_METHOD_RESULTS,
+ CALLBACK_METHOD_RESULTS)
+ );
+ }
+
+ @Test
+ public void sequenceTest_startListening_cancel() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_CANCEL),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_UNSPECIFIED,
+ CALLBACK_METHOD_UNSPECIFIED),
+ /* expected service methods propagated: */ ImmutableList.of(true, true),
+ /* expected callback methods invoked: */ ImmutableList.of()
+ );
+ }
+
+ @Test
+ public void sequenceTest_startListening_startListening() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_START_LISTENING),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_UNSPECIFIED),
+ /* expected service methods propagated: */ ImmutableList.of(true, false),
+ /* expected callback methods invoked: */ ImmutableList.of(
+ CALLBACK_METHOD_ERROR)
+ );
+ }
+
+ @Test
+ public void sequenceTest_startListening_stopListening_cancel() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_STOP_LISTENING,
+ RECOGNIZER_METHOD_CANCEL),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_UNSPECIFIED,
+ CALLBACK_METHOD_UNSPECIFIED,
+ CALLBACK_METHOD_UNSPECIFIED),
+ /* expected service methods propagated: */ ImmutableList.of(true, true, true),
+ /* expected callback methods invoked: */ ImmutableList.of()
+ );
+ }
+
+ @Test
+ public void sequenceTest_startListening_error_cancel() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_CANCEL),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_ERROR),
+ /* expected service methods propagated: */ ImmutableList.of(true, false),
+ /* expected callback methods invoked: */ ImmutableList.of(
+ CALLBACK_METHOD_ERROR)
+ );
+ }
+
+ @Test
+ public void sequenceTest_startListening_stopListening_destroy() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_STOP_LISTENING,
+ RECOGNIZER_METHOD_DESTROY),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_UNSPECIFIED,
+ CALLBACK_METHOD_UNSPECIFIED,
+ CALLBACK_METHOD_UNSPECIFIED),
+ /* expected service methods propagated: */ ImmutableList.of(true, true, true),
+ /* expected callback methods invoked: */ ImmutableList.of()
+ );
+ }
+
+ @Test
+ public void sequenceTest_startListening_error_destroy() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_DESTROY),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_ERROR),
+ /* expected service methods propagated: */ ImmutableList.of(true, false),
+ /* expected callback methods invoked: */ ImmutableList.of(
+ CALLBACK_METHOD_ERROR)
+ );
+ }
+
+ @Test
+ public void sequenceTest_startListening_destroy_destroy() {
+ executeSequenceTest(
+ /* service methods to call: */ ImmutableList.of(
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_DESTROY,
+ RECOGNIZER_METHOD_DESTROY),
+ /* callback methods to call: */ ImmutableList.of(
+ CALLBACK_METHOD_UNSPECIFIED,
+ CALLBACK_METHOD_UNSPECIFIED),
+ /* expected service methods propagated: */ ImmutableList.of(true, true, false),
+ /* expected callback methods invoked: */ ImmutableList.of()
+ );
+ }
+
+ private void executeSequenceTest(
+ List<RecognizerMethod> recognizerMethodsToCall,
+ List<CallbackMethod> callbackMethodInstructions,
+ List<Boolean> expectedRecognizerServiceMethodsToPropagate,
+ List<CallbackMethod> expectedClientCallbackMethods) {
+ setCurrentRecognizer(IN_PACKAGE_RECOGNITION_SERVICE);
+ mUiDevice.waitForIdle();
+
+ mActivity.mCallbackMethodsInvoked.clear();
+ CtsRecognitionService.sInvokedRecognizerMethods.clear();
+ CtsRecognitionService.sInstructedCallbackMethods.clear();
+ CtsRecognitionService.sInstructedCallbackMethods.addAll(callbackMethodInstructions);
+
+ List<RecognizerMethod> expectedServiceMethods = new ArrayList<>();
+
+ for (int i = 0; i < recognizerMethodsToCall.size(); i++) {
+ RecognizerMethod recognizerMethod = recognizerMethodsToCall.get(i);
+ Log.i(TAG, "Sending service method " + recognizerMethod.name());
+
+ switch (recognizerMethod) {
+ case RECOGNIZER_METHOD_UNSPECIFIED:
+ fail();
+ break;
+ case RECOGNIZER_METHOD_START_LISTENING:
+ mActivity.startListening(START_LISTENING_INTENT);
+ break;
+ case RECOGNIZER_METHOD_STOP_LISTENING:
+ mActivity.stopListening();
+ break;
+ case RECOGNIZER_METHOD_CANCEL:
+ mActivity.cancel();
+ break;
+ case RECOGNIZER_METHOD_DESTROY:
+ mActivity.destroyRecognizer();
+ break;
+ default:
+ fail();
+ }
+
+ if (expectedRecognizerServiceMethodsToPropagate.get(i)) {
+ expectedServiceMethods.add(
+ RECOGNIZER_METHOD_DESTROY != recognizerMethod
+ ? recognizerMethod
+ : RECOGNIZER_METHOD_CANCEL);
+ PollingCheck.waitFor(SEQUENCE_TEST_WAIT_TIMEOUT_MS,
+ () -> CtsRecognitionService.sInvokedRecognizerMethods.size()
+ == expectedServiceMethods.size());
+ }
+ }
+
+ PollingCheck.waitFor(SEQUENCE_TEST_WAIT_TIMEOUT_MS,
+ () -> CtsRecognitionService.sInstructedCallbackMethods.isEmpty());
+ PollingCheck.waitFor(SEQUENCE_TEST_WAIT_TIMEOUT_MS,
+ () -> mActivity.mCallbackMethodsInvoked.size()
+ >= expectedClientCallbackMethods.size());
+
+ assertThat(CtsRecognitionService.sInvokedRecognizerMethods).isEqualTo(expectedServiceMethods);
+ assertThat(mActivity.mCallbackMethodsInvoked).isEqualTo(expectedClientCallbackMethods);
+ assertThat(CtsRecognitionService.sInstructedCallbackMethods).isEmpty();
+ }
+
+ private static void prepareDevice() {
+ // Unlock screen.
+ runShellCommand("input keyevent KEYCODE_WAKEUP");
+ // Dismiss keyguard, in case it's set as "Swipe to unlock".
+ runShellCommand("wm dismiss-keyguard");
+ }
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CallbackMethod.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CallbackMethod.java
new file mode 100644
index 0000000..b8622f4
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CallbackMethod.java
@@ -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 android.voicerecognition.cts;
+
+enum CallbackMethod {
+ CALLBACK_METHOD_UNSPECIFIED,
+ CALLBACK_METHOD_BEGINNING_OF_SPEECH,
+ CALLBACK_METHOD_BUFFER_RECEIVED,
+ CALLBACK_METHOD_END_OF_SPEECH,
+ CALLBACK_METHOD_ERROR,
+ CALLBACK_METHOD_PARTIAL_RESULTS,
+ CALLBACK_METHOD_READY_FOR_SPEECH,
+ CALLBACK_METHOD_RESULTS,
+ CALLBACK_METHOD_RMS_CHANGED
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CtsRecognitionService.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CtsRecognitionService.java
new file mode 100644
index 0000000..5627c9d
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CtsRecognitionService.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voicerecognition.cts;
+
+import static android.voicerecognition.cts.TestObjects.ERROR_CODE;
+import static android.voicerecognition.cts.TestObjects.PARTIAL_RESULTS_BUNDLE;
+import static android.voicerecognition.cts.TestObjects.READY_FOR_SPEECH_BUNDLE;
+import static android.voicerecognition.cts.TestObjects.RESULTS_BUNDLE;
+import static android.voicerecognition.cts.TestObjects.RMS_CHANGED_VALUE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.content.Intent;
+import android.os.RemoteException;
+import android.speech.RecognitionService;
+import android.util.Log;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class CtsRecognitionService extends RecognitionService {
+ private static final String TAG = CtsRecognitionService.class.getSimpleName();
+
+ public static List<RecognizerMethod> sInvokedRecognizerMethods = new ArrayList<>();
+ public static Queue<CallbackMethod> sInstructedCallbackMethods = new ArrayDeque<>();
+ public static AtomicBoolean sIsActive = new AtomicBoolean(false);
+
+ private final Random mRandom = new Random();
+
+ @Override
+ protected void onStartListening(Intent recognizerIntent, Callback listener) {
+ sIsActive.set(true);
+ assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
+
+ sInvokedRecognizerMethods.add(RecognizerMethod.RECOGNIZER_METHOD_START_LISTENING);
+
+ maybeRespond(listener);
+ sIsActive.set(false);
+ }
+
+ @Override
+ protected void onStopListening(Callback listener) {
+ sIsActive.set(true);
+ assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
+
+ sInvokedRecognizerMethods.add(RecognizerMethod.RECOGNIZER_METHOD_STOP_LISTENING);
+
+ maybeRespond(listener);
+ sIsActive.set(false);
+ }
+
+ @Override
+ protected void onCancel(Callback listener) {
+ sIsActive.set(true);
+ assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
+
+ sInvokedRecognizerMethods.add(RecognizerMethod.RECOGNIZER_METHOD_CANCEL);
+
+ maybeRespond(listener);
+ sIsActive.set(false);
+ }
+
+ private void maybeRespond(Callback listener) {
+ if (sInstructedCallbackMethods.isEmpty()) {
+ return;
+ }
+
+ CallbackMethod callbackMethod = sInstructedCallbackMethods.poll();
+
+ Log.i(TAG, "Responding with callback method " + callbackMethod.name());
+
+ try {
+ switch (callbackMethod) {
+ case CALLBACK_METHOD_UNSPECIFIED:
+ // ignore
+ break;
+ case CALLBACK_METHOD_BEGINNING_OF_SPEECH:
+ listener.beginningOfSpeech();
+ break;
+ case CALLBACK_METHOD_BUFFER_RECEIVED:
+ byte[] buffer = new byte[100];
+ mRandom.nextBytes(buffer);
+ listener.bufferReceived(buffer);
+ break;
+ case CALLBACK_METHOD_END_OF_SPEECH:
+ listener.endOfSpeech();
+ break;
+ case CALLBACK_METHOD_ERROR:
+ listener.error(ERROR_CODE);
+ break;
+ case CALLBACK_METHOD_RESULTS:
+ listener.results(RESULTS_BUNDLE);
+ break;
+ case CALLBACK_METHOD_PARTIAL_RESULTS:
+ listener.partialResults(PARTIAL_RESULTS_BUNDLE);
+ break;
+ case CALLBACK_METHOD_READY_FOR_SPEECH:
+ listener.readyForSpeech(READY_FOR_SPEECH_BUNDLE);
+ break;
+ case CALLBACK_METHOD_RMS_CHANGED:
+ listener.rmsChanged(RMS_CHANGED_VALUE);
+ break;
+ default:
+ fail();
+ }
+ } catch (RemoteException e) {
+ fail();
+ }
+ }
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/DefaultRecognitionServiceTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/DefaultRecognitionServiceTest.java
new file mode 100644
index 0000000..b8e3116
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/DefaultRecognitionServiceTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.voicerecognition.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+/** Recognition service tests for a default speech recognition service. */
+@RunWith(AndroidJUnit4.class)
+public final class DefaultRecognitionServiceTest extends AbstractRecognitionServiceTest {
+
+ // same as Settings.Secure.VOICE_RECOGNITION_SERVICE
+ final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
+
+ protected final Context mContext = InstrumentationRegistry.getTargetContext();
+ private final String mOriginalVoiceRecognizer = Settings.Secure.getString(
+ mContext.getContentResolver(), VOICE_RECOGNITION_SERVICE);
+
+ @Rule
+ public final SettingsStateChangerRule mVoiceRecognitionServiceSetterRule =
+ new SettingsStateChangerRule(mContext, VOICE_RECOGNITION_SERVICE,
+ mOriginalVoiceRecognizer);
+
+ @Override
+ protected void setCurrentRecognizer(String recognizer) {
+ runWithShellPermissionIdentity(
+ () -> Settings.Secure.putString(mContext.getContentResolver(),
+ VOICE_RECOGNITION_SERVICE, recognizer));
+ }
+
+ @Override
+ boolean isOnDeviceTest() {
+ return false;
+ }
+
+ @Override
+ String customRecognizer() {
+ // We will use the default one (specified in secure settings).
+ return null;
+ }
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
index f11cef4..663d4bd 100644
--- a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
@@ -50,6 +50,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+import java.util.stream.Collectors;
+
@RunWith(AndroidJUnit4.class)
public final class RecognitionServiceMicIndicatorTest {
@@ -87,7 +90,7 @@
new ActivityTestRule<>(SpeechRecognitionActivity.class);
@Rule
- public final SettingsStateChangerRule mVoiceRcognitionrviceSetterRule =
+ public final SettingsStateChangerRule mVoiceRecognitionServiceSetterRule =
new SettingsStateChangerRule(mContext, VOICE_RECOGNITION_SERVICE,
mOriginalVoiceRecognizer);
@@ -96,6 +99,7 @@
prepareDevice();
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mActivity = mActivityTestRule.getActivity();
+ mActivity.init(false, null);
final PackageManager pm = mContext.getPackageManager();
try {
@@ -157,18 +161,18 @@
}
@Test
- public void testNonTrustedRecognitionServiceCannotBlameCallingApp() throws Throwable {
+ public void testNonTrustedRecognitionServiceCanBlameCallingApp() throws Throwable {
// This is a workaound solution for R QPR. We treat trusted if the current voice recognizer
// is also a preinstalled app. This is a untrusted case.
setCurrentRecognizer(CTS_VOICE_RECOGNITION_SERVICE);
// verify that the untrusted app cannot blame the calling app mic access
- testVoiceRecognitionServiceBlameCallingApp(/* trustVoiceService */ false);
+ testVoiceRecognitionServiceBlameCallingApp(/* trustVoiceService */ true);
}
@Test
public void testTrustedRecognitionServiceCanBlameCallingApp() throws Throwable {
- // This is a workaound solution for R QPR. We treat trusted if the current voice recognizer
+ // This is a workaround solution for R QPR. We treat trusted if the current voice recognizer
// is also a preinstalled app. This is a trusted case.
boolean hasPreInstalledRecognizer = hasPreInstalledRecognizer(
getComponentPackageNameFromString(mOriginalVoiceRecognizer));
@@ -220,12 +224,18 @@
SystemClock.sleep(UI_WAIT_TIMEOUT);
// Make sure dialog is shown
- final UiObject2 recognitionCallingAppLabel = mUiDevice.findObject(
+ List<UiObject2> recognitionCallingAppLabels = mUiDevice.findObjects(
By.res(PRIVACY_DIALOG_PACKAGE_NAME, PRIVACY_DIALOG_CONTENT_ID));
assertWithMessage("No permission dialog shown after clicking privacy chip.").that(
- recognitionCallingAppLabel).isNotNull();
+ recognitionCallingAppLabels).isNotEmpty();
+
// get dialog content
- final String dialogDescription = recognitionCallingAppLabel.getText();
+ final String dialogDescription =
+ recognitionCallingAppLabels
+ .stream()
+ .map(UiObject2::getText)
+ .collect(Collectors.joining("\n"));
+ Log.i(TAG, "Retrieved dialog description " + dialogDescription);
if (trustVoiceService) {
// Check trust recognizer can blame calling apmic permission
assertWithMessage(
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceTest.java
deleted file mode 100644
index 2ba3815..0000000
--- a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceTest.java
+++ /dev/null
@@ -1,111 +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.voicerecognition.cts;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Context;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.support.test.uiautomator.UiDevice;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.SettingsStateChangerRule;
-
-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.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public final class RecognitionServiceTest {
-
- private final String TAG = "RecognitionServiceTest";
- // same as Settings.Secure.VOICE_RECOGNITION_SERVICE
- final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
- // A simple test voice recognition service implementation
- private final String CTS_VOICE_RECOGNITION_SERVICE =
- "android.recognitionservice.service/android.recognitionservice.service"
- + ".CtsVoiceRecognitionService";
-
- private final long INDICATOR_DISMISS_TIMEOUT = 5000L;
- private final long WAIT_TIMEOUT_MS = 30000L; // 30 secs
-
- protected final Context mContext = InstrumentationRegistry.getTargetContext();
- private final String mOriginalVoiceRecognizer = Settings.Secure.getString(
- mContext.getContentResolver(), VOICE_RECOGNITION_SERVICE);
-
- UiDevice mUiDevice;
- SpeechRecognitionActivity mActivity;
-
- @Rule
- public ActivityTestRule<SpeechRecognitionActivity> mActivityTestRule =
- new ActivityTestRule<>(SpeechRecognitionActivity.class);
-
- @Rule
- public final SettingsStateChangerRule mVoiceRcognitionrviceSetterRule =
- new SettingsStateChangerRule(mContext, VOICE_RECOGNITION_SERVICE,
- mOriginalVoiceRecognizer);
-
- @Before
- public void setup() {
- prepareDevice();
- mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- mActivity = mActivityTestRule.getActivity();
- }
-
- @Test
- public void testStartListening() throws Throwable {
- setCurrentRecognizer(CTS_VOICE_RECOGNITION_SERVICE);
- mActivity.startListening();
- try {
- // startListening() will call noteProxyOpNoTrow(), if the permission check pass then the
- // RecognitionService.onStartListening() will be called. Otherwise, a TimeoutException
- // will be thrown.
- assertThat(mActivity.mCountDownLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS))
- .isTrue();
- } catch (InterruptedException e) {
- assertWithMessage("onStartListening() not called. " + e).fail();
- }
- // Wait for the privacy indicator to disappear to avoid the test becoming flaky.
- SystemClock.sleep(INDICATOR_DISMISS_TIMEOUT);
- }
-
- void setCurrentRecognizer(String recognizer) {
- runWithShellPermissionIdentity(
- () -> Settings.Secure.putString(mContext.getContentResolver(),
- VOICE_RECOGNITION_SERVICE, recognizer));
- mUiDevice.waitForIdle();
- }
-
- private void prepareDevice() {
- // Unlock screen.
- runShellCommand("input keyevent KEYCODE_WAKEUP");
- // Dismiss keyguard, in case it's set as "Swipe to unlock".
- runShellCommand("wm dismiss-keyguard");
- }
-}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognizerMethod.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognizerMethod.java
new file mode 100644
index 0000000..9f673d9
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognizerMethod.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.voicerecognition.cts;
+
+enum RecognizerMethod {
+ RECOGNIZER_METHOD_UNSPECIFIED,
+ RECOGNIZER_METHOD_START_LISTENING,
+ RECOGNIZER_METHOD_STOP_LISTENING,
+ RECOGNIZER_METHOD_CANCEL,
+ RECOGNIZER_METHOD_DESTROY
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java
index 7e03261..6736280 100644
--- a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java
@@ -17,13 +17,17 @@
package android.voicerecognition.cts;
import android.app.Activity;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
@@ -39,6 +43,8 @@
private Handler mHandler;
private SpeechRecognizerListener mListener;
+ final List<CallbackMethod> mCallbackMethodsInvoked = new ArrayList<>();
+
public boolean mStartListeningCalled;
public CountDownLatch mCountDownLatch;
@@ -47,7 +53,6 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
- init();
}
@Override
@@ -60,72 +65,95 @@
}
public void startListening() {
- mHandler.post(() -> {
- if (mRecognizer != null) {
- final Intent recognizerIntent =
- new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
- recognizerIntent.putExtra(
- RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());
- mRecognizer.startListening(recognizerIntent);
- }
- });
+ final Intent recognizerIntent =
+ new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ recognizerIntent.putExtra(
+ RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());
+ startListening(recognizerIntent);
}
- private void init() {
+ public void startListening(Intent intent) {
+ mHandler.post(() -> mRecognizer.startListening(intent));
+ }
+
+ public void stopListening() {
+ mHandler.post(mRecognizer::stopListening);
+ }
+
+ public void cancel() {
+ mHandler.post(mRecognizer::cancel);
+ }
+
+ public void destroyRecognizer() {
+ mHandler.post(mRecognizer::destroy);
+ }
+
+ public void init(boolean onDevice, String customRecgonizerComponent) {
mHandler = new Handler(getMainLooper());
- mRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
- mListener = new SpeechRecognizerListener();
- mRecognizer.setRecognitionListener(mListener);
- mStartListeningCalled = false;
- mCountDownLatch = new CountDownLatch(1);
+ mHandler.post(() -> {
+ if (onDevice) {
+ mRecognizer = SpeechRecognizer.createOnDeviceSpeechRecognizer(this);
+ } else if (customRecgonizerComponent != null) {
+ mRecognizer = SpeechRecognizer.createSpeechRecognizer(this,
+ ComponentName.unflattenFromString(customRecgonizerComponent));
+ } else {
+ mRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
+ }
+
+ mListener = new SpeechRecognizerListener();
+ mRecognizer.setRecognitionListener(mListener);
+ mRecognizer.setRecognitionListener(mListener);
+ mStartListeningCalled = false;
+ mCountDownLatch = new CountDownLatch(1);
+ });
}
private class SpeechRecognizerListener implements RecognitionListener {
@Override
public void onReadyForSpeech(Bundle params) {
-
+ mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_READY_FOR_SPEECH);
}
@Override
public void onBeginningOfSpeech() {
-
+ mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_BEGINNING_OF_SPEECH);
}
@Override
public void onRmsChanged(float rmsdB) {
-
+ mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_RMS_CHANGED);
}
@Override
public void onBufferReceived(byte[] buffer) {
-
+ mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_BUFFER_RECEIVED);
}
@Override
public void onEndOfSpeech() {
-
+ mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_END_OF_SPEECH);
}
@Override
public void onError(int error) {
-
+ mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_ERROR);
}
@Override
public void onResults(Bundle results) {
+ mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_RESULTS);
mStartListeningCalled = true;
mCountDownLatch.countDown();
}
@Override
public void onPartialResults(Bundle partialResults) {
-
+ mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_PARTIAL_RESULTS);
}
@Override
public void onEvent(int eventType, Bundle params) {
-
}
}
}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/TestObjects.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/TestObjects.java
new file mode 100644
index 0000000..b507f21
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/TestObjects.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.voicerecognition.cts;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.speech.RecognizerIntent;
+
+class TestObjects {
+ public static final int ERROR_CODE = 42;
+ public static final float RMS_CHANGED_VALUE = 13.13f;
+
+ public static final Bundle RESULTS_BUNDLE = new Bundle();
+ static {
+ RESULTS_BUNDLE.putChar("a", 'a');
+ }
+ public static final Bundle PARTIAL_RESULTS_BUNDLE = new Bundle();
+ static {
+ PARTIAL_RESULTS_BUNDLE.putChar("b", 'b');
+ }
+ public static final Bundle READY_FOR_SPEECH_BUNDLE = new Bundle();
+ static {
+ READY_FOR_SPEECH_BUNDLE.putChar("c", 'c');
+ }
+ public static final Intent START_LISTENING_INTENT =
+ new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ static {
+ START_LISTENING_INTENT.putExtra("d", 'd');
+ }
+}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
index fc883fd..4af3b90 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
@@ -16,6 +16,8 @@
package android.voiceinteraction.cts;
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -47,6 +49,7 @@
public class DirectActionsTest extends AbstractVoiceInteractionTestCase {
private static final String TAG = DirectActionsTest.class.getSimpleName();
private static final long OPERATION_TIMEOUT_MS = 5000;
+ private static final String TEST_APP_PACKAGE = "android.voiceinteraction.testapp";
private final @NonNull SessionControl mSessionControl = new SessionControl();
private final @NonNull ActivityControl mActivityControl = new ActivityControl();
@@ -253,13 +256,18 @@
+ "/DirectActionsActivity"))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(Utils.DIRECT_ACTIONS_KEY_CALLBACK, callback);
-
- if (!mContext.getPackageManager().isInstantApp()) {
- intent.setPackage("android.voiceinteraction.testapp");
+ if (mContext.getPackageManager().isInstantApp()) {
+ // Override app-links domain verification.
+ runShellCommand(
+ String.format(
+ "pm set-app-links-user-selection --user cur --package %1$s true"
+ + " %1$s",
+ TEST_APP_PACKAGE));
+ } else {
+ intent.setPackage(TEST_APP_PACKAGE);
}
Log.v(TAG, "startActivity: " + intent);
-
mContext.startActivity(intent);
if (!latch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java
index 03ace91..0d1533a 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Intent;
+import android.platform.test.annotations.AppModeFull;
import android.service.voice.VoiceInteractionService;
import android.voiceinteraction.common.Utils;
@@ -33,6 +34,7 @@
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "No real use case for instant mode hotword detection service")
public final class HotwordDetectionServiceTest extends AbstractVoiceInteractionTestCase {
static final String TAG = "HotwordDetectionServiceTest";
diff --git a/tests/tests/widget/res/layout/analogclock_layout.xml b/tests/tests/widget/res/layout/analogclock_layout.xml
index 2f2c5a7..b5fa004 100644
--- a/tests/tests/widget/res/layout/analogclock_layout.xml
+++ b/tests/tests/widget/res/layout/analogclock_layout.xml
@@ -28,9 +28,17 @@
android:layout_width="60dp"
android:layout_height="60dp"
android:dial="@drawable/blue_fill"
+ android:dialTint="@color/testcolorstatelist1"
+ android:dialTintMode="src_in"
android:hand_hour="@drawable/green_fill"
+ android:hand_hourTint="@color/testcolor1"
+ android:hand_hourTintMode="src_over"
android:hand_minute="@drawable/magenta_fill"
+ android:hand_minuteTint="@color/testcolor2"
+ android:hand_minuteTintMode="screen"
android:hand_second="@drawable/yellow_fill"
+ android:hand_secondTint="@color/testcolor3"
+ android:hand_secondTintMode="add"
android:timeZone="America/New_York"/>
</LinearLayout>
diff --git a/tests/tests/widget/res/layout/listview_layout.xml b/tests/tests/widget/res/layout/listview_layout.xml
index 79669c1..7f4872d 100644
--- a/tests/tests/widget/res/layout/listview_layout.xml
+++ b/tests/tests/widget/res/layout/listview_layout.xml
@@ -58,5 +58,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
+ <ListView
+ android:id="@+id/listview_stretch"
+ android:layout_width="90px"
+ android:layout_height="90px"
+ android:edgeEffectType="stretch"/>
+
</FrameLayout>
diff --git a/tests/tests/widget/res/values/colors.xml b/tests/tests/widget/res/values/colors.xml
index f104a6d2..c947b7a 100644
--- a/tests/tests/widget/res/values/colors.xml
+++ b/tests/tests/widget/res/values/colors.xml
@@ -22,6 +22,7 @@
<drawable name="yellow">#77ffff00</drawable>
<color name="testcolor1">#ff00ff00</color>
<color name="testcolor2">#ffff0000</color>
+ <color name="testcolor3">#ff0000ff</color>
<color name="failColor">#ff0000ff</color>
<color name="calendarview_week_background">#40FF0000</color>
diff --git a/tests/tests/widget/res/values/dimens.xml b/tests/tests/widget/res/values/dimens.xml
index 685e2c1..0b501af 100644
--- a/tests/tests/widget/res/values/dimens.xml
+++ b/tests/tests/widget/res/values/dimens.xml
@@ -44,6 +44,8 @@
<dimen name="textview_padding_top">2dip</dimen>
<dimen name="textview_padding_bottom">4dip</dimen>
<dimen name="textview_drawable_padding">2dip</dimen>
+ <dimen name="textview_fixed_width">100dp</dimen>
+ <dimen name="textview_fixed_height">200dp</dimen>
<dimen name="textview_firstBaselineToTopHeight">100dp</dimen>
<dimen name="textview_lastBaselineToBottomHeight">30dp</dimen>
diff --git a/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java b/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java
index b58ab3d..ae22d58 100644
--- a/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java
@@ -19,7 +19,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import android.annotation.ColorRes;
import android.app.Activity;
+import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
+import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.Xml;
@@ -153,4 +157,140 @@
mClock.setTimeZone(null);
assertNull(mClock.getTimeZone());
}
+
+ @Test
+ public void testSetDialTintList() {
+ assertNull(mClock.getDialTintList());
+ assertEquals(
+ getColorStateList(R.color.testcolorstatelist1),
+ mClockWithAttrs.getDialTintList());
+
+ ColorStateList tintList = new ColorStateList(
+ new int[][] { {android.R.attr.state_checked}, {}},
+ new int[] {Color.RED, Color.BLUE});
+ mClock.setDialTintList(tintList);
+ assertEquals(tintList, mClock.getDialTintList());
+
+ mClock.setDialTintList(null);
+ assertNull(mClock.getDialTintList());
+ }
+
+ @Test
+ public void testSetDialTintBlendMode() {
+ assertNull(mClock.getDialTintBlendMode());
+ assertEquals(BlendMode.SRC_IN, mClockWithAttrs.getDialTintBlendMode());
+
+ mClock.setDialTintBlendMode(BlendMode.COLOR);
+ mClockWithAttrs.setDialTintBlendMode(BlendMode.COLOR);
+ assertEquals(BlendMode.COLOR, mClock.getDialTintBlendMode());
+ assertEquals(BlendMode.COLOR, mClockWithAttrs.getDialTintBlendMode());
+
+ mClock.setDialTintBlendMode(null);
+ mClockWithAttrs.setDialTintBlendMode(null);
+ assertNull(mClock.getDialTintBlendMode());
+ assertNull(mClockWithAttrs.getDialTintBlendMode());
+ }
+
+ @Test
+ public void testSetHourHandTintList() {
+ assertNull(mClock.getHourHandTintList());
+ assertEquals(
+ getColorStateList(R.color.testcolor1),
+ mClockWithAttrs.getHourHandTintList());
+
+ ColorStateList tintList = new ColorStateList(
+ new int[][] { {android.R.attr.state_checked}, {}},
+ new int[] {Color.BLACK, Color.WHITE});
+ mClock.setHourHandTintList(tintList);
+ assertEquals(tintList, mClock.getHourHandTintList());
+
+ mClock.setHourHandTintList(null);
+ assertNull(mClock.getHourHandTintList());
+ }
+
+ @Test
+ public void testSetHourHandTintBlendMode() {
+ assertNull(mClock.getHourHandTintBlendMode());
+ assertEquals(BlendMode.SRC_OVER, mClockWithAttrs.getHourHandTintBlendMode());
+
+ mClock.setHourHandTintBlendMode(BlendMode.COLOR_BURN);
+ mClockWithAttrs.setHourHandTintBlendMode(BlendMode.COLOR_BURN);
+ assertEquals(BlendMode.COLOR_BURN, mClock.getHourHandTintBlendMode());
+ assertEquals(BlendMode.COLOR_BURN, mClockWithAttrs.getHourHandTintBlendMode());
+
+ mClock.setHourHandTintBlendMode(null);
+ mClockWithAttrs.setHourHandTintBlendMode(null);
+ assertNull(mClock.getHourHandTintBlendMode());
+ assertNull(mClockWithAttrs.getHourHandTintBlendMode());
+ }
+
+ @Test
+ public void testSetMinuteHandTintList() {
+ assertNull(mClock.getMinuteHandTintList());
+ assertEquals(
+ getColorStateList(R.color.testcolor2),
+ mClockWithAttrs.getMinuteHandTintList());
+
+ ColorStateList tintList = new ColorStateList(
+ new int[][] { {android.R.attr.state_active}, {}},
+ new int[] {Color.CYAN, Color.BLUE});
+ mClock.setMinuteHandTintList(tintList);
+ assertEquals(tintList, mClock.getMinuteHandTintList());
+
+ mClock.setMinuteHandTintList(null);
+ assertNull(mClock.getMinuteHandTintList());
+ }
+
+ @Test
+ public void testSetMinuteHandTintBlendMode() {
+ assertNull(mClock.getMinuteHandTintBlendMode());
+ assertEquals(BlendMode.SCREEN, mClockWithAttrs.getMinuteHandTintBlendMode());
+
+ mClock.setMinuteHandTintBlendMode(BlendMode.COLOR_DODGE);
+ mClockWithAttrs.setMinuteHandTintBlendMode(BlendMode.COLOR_DODGE);
+ assertEquals(BlendMode.COLOR_DODGE, mClock.getMinuteHandTintBlendMode());
+ assertEquals(BlendMode.COLOR_DODGE, mClockWithAttrs.getMinuteHandTintBlendMode());
+
+ mClock.setMinuteHandTintBlendMode(null);
+ mClockWithAttrs.setMinuteHandTintBlendMode(null);
+ assertNull(mClock.getMinuteHandTintBlendMode());
+ assertNull(mClockWithAttrs.getMinuteHandTintBlendMode());
+ }
+
+ @Test
+ public void testSetSecondHandTintList() {
+ assertNull(mClock.getSecondHandTintList());
+ assertEquals(
+ getColorStateList(R.color.testcolor3),
+ mClockWithAttrs.getSecondHandTintList());
+
+ ColorStateList tintList = new ColorStateList(
+ new int[][] { {android.R.attr.state_checked}, {}},
+ new int[] {Color.GREEN, Color.BLUE});
+ mClock.setSecondHandTintList(tintList);
+ assertEquals(tintList, mClock.getSecondHandTintList());
+
+ mClock.setSecondHandTintList(null);
+ assertNull(mClock.getSecondHandTintList());
+ }
+
+ @Test
+ public void testSetSecondHandTintBlendMode() {
+ assertNull(mClock.getSecondHandTintBlendMode());
+ assertEquals(BlendMode.PLUS, mClockWithAttrs.getSecondHandTintBlendMode());
+
+ mClock.setSecondHandTintBlendMode(BlendMode.DARKEN);
+ mClockWithAttrs.setSecondHandTintBlendMode(BlendMode.DARKEN);
+ assertEquals(BlendMode.DARKEN, mClock.getSecondHandTintBlendMode());
+ assertEquals(BlendMode.DARKEN, mClockWithAttrs.getSecondHandTintBlendMode());
+
+ mClock.setSecondHandTintBlendMode(null);
+ mClockWithAttrs.setSecondHandTintBlendMode(null);
+ assertNull(mClock.getSecondHandTintBlendMode());
+ assertNull(mClockWithAttrs.getSecondHandTintBlendMode());
+ }
+
+ private ColorStateList getColorStateList(@ColorRes int resId) {
+ return mClock.getContext().getColorStateList(resId);
+ }
}
diff --git a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
index c468cc9..ec3856c 100644
--- a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
@@ -28,21 +28,18 @@
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
-import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Xml;
-import android.view.PixelCopy;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
import android.widget.EdgeEffect;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.TextView;
+import android.widget.cts.util.StretchEdgeUtil;
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
@@ -51,9 +48,7 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.CtsTouchUtils;
import com.android.compatibility.common.util.PollingCheck;
-import com.android.compatibility.common.util.SynchronousPixelCopy;
import com.android.compatibility.common.util.WidgetTestUtils;
import org.junit.Before;
@@ -62,6 +57,8 @@
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
+import java.util.ArrayList;
+
/**
* Test {@link HorizontalScrollView}.
*/
@@ -807,121 +804,32 @@
// Make sure that the scroll view we care about is on screen and at the left:
showOnlyStretch();
- int[] locationOnScreen = new int[2];
- mActivityRule.runOnUiThread(() -> {
- mScrollViewStretch.getLocationOnScreen(locationOnScreen);
- });
-
- Bitmap[] bitmap = new Bitmap[1];
-
- int width = mScrollViewStretch.getWidth();
- int height = mScrollViewStretch.getHeight();
-
- CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
- locationOnScreen[0],
- locationOnScreen[1],
- 50,
- 0,
- 50,
- 5,
- new CtsTouchUtils.EventInjectionListener() {
- private int mNumEvents = 0;
-
- @Override
- public void onDownInjected(int xOnScreen, int yOnScreen) {
- }
-
- @Override
- public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
- mNumEvents++;
- if (mNumEvents == 5) {
- // Have to take the picture after drag, but before the up event
- bitmap[0] = takeScreenshot(locationOnScreen[0], locationOnScreen[1],
- width,
- height);
- }
- }
-
- @Override
- public void onUpInjected(int xOnScreen, int yOnScreen) {
- }
- });
-
- // The blue should stretch beyond its normal dimensions. Avoid the shadow at the top.
- assertEquals(Color.BLUE, bitmap[0].getPixel(52, 49));
+ assertTrue(StretchEdgeUtil.dragRightStretches(mActivityRule, mScrollViewStretch));
}
// If this test is showing as flaky, it is more likely that it is broken. I've
// leaned toward false positive over false negative.
@LargeTest
@Test
- public void testStretchLeftAndCatch() throws Throwable {
+ public void testStretchAtLeftAndCatch() throws Throwable {
// Make sure that the scroll view we care about is on screen and at the top:
showOnlyStretch();
- int[] locationOnScreen = new int[2];
- mActivityRule.runOnUiThread(() -> {
- mScrollViewStretch.getLocationOnScreen(locationOnScreen);
- });
+ assertTrue(StretchEdgeUtil.dragRightTapAndHoldStretches(mActivityRule, mScrollViewStretch));
+ }
- boolean[] wasDownDoneInTime = new boolean[1];
+ @Test
+ public void testEdgeEffectType() {
+ // Should default to "glow"
+ assertEquals(EdgeEffect.TYPE_GLOW, mScrollViewRegular.getEdgeEffectType());
- // Try at most 5 times. Give slow devices their best chance to respond, but
- // don't penalize them with a flaky test if they can't complete in time.
- for (int i = 0; i < 5 && !wasDownDoneInTime[0]; i++) {
- CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
- locationOnScreen[0],
- locationOnScreen[1],
- 89,
- 0,
- 50,
- 5,
- new CtsTouchUtils.EventInjectionListener() {
- private long mLastAnimationTime;
+ // This one has "stretch" attribute
+ assertEquals(EdgeEffect.TYPE_STRETCH, mScrollViewStretch.getEdgeEffectType());
- @Override
- public void onDownInjected(int xOnScreen, int yOnScreen) {
- }
-
- @Override
- public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
- mLastAnimationTime = AnimationUtils.currentAnimationTimeMillis();
- }
-
- @Override
- public void onUpInjected(int xOnScreen, int yOnScreen) {
- CtsTouchUtils.injectDownEvent(mInstrumentation.getUiAutomation(),
- SystemClock.uptimeMillis(), locationOnScreen[0],
- locationOnScreen[1], null);
- long animationTime = AnimationUtils.currentAnimationTimeMillis();
- // The receding time is 600 ms, but we don't want to be near the final
- // part of the animation when the pixels may overlap.
- if (animationTime - mLastAnimationTime < 400) {
- wasDownDoneInTime[0] = true;
- }
- }
- });
-
- // To avoid flaky tests, this ensures that the down event was received before the
- // recede went too far.
- if (wasDownDoneInTime[0]) {
- // Now make sure that we wait until the release should normally have finished:
- sleepAnimationTime(600);
-
- // Since we caught it, it should be held and still stretched.
- int width = mScrollViewStretch.getWidth();
- int height = mScrollViewStretch.getHeight();
-
- Bitmap bitmap = takeScreenshot(locationOnScreen[0], locationOnScreen[1], width,
- height);
-
- // The blue should still be stretched
- assertEquals(Color.BLUE, bitmap.getPixel(52, 49));
- }
- CtsTouchUtils.injectUpEvent(mInstrumentation.getUiAutomation(),
- SystemClock.uptimeMillis(), false,
- locationOnScreen[0], locationOnScreen[1], null);
- }
+ mScrollViewStretch.setEdgeEffectType(EdgeEffect.TYPE_GLOW);
+ assertEquals(EdgeEffect.TYPE_GLOW, mScrollViewStretch.getEdgeEffectType());
+ mScrollViewStretch.setEdgeEffectType(EdgeEffect.TYPE_STRETCH);
+ assertEquals(EdgeEffect.TYPE_STRETCH, mScrollViewStretch.getEdgeEffectType());
}
@Test
@@ -934,54 +842,14 @@
mScrollViewStretch.scrollTo(210, 0);
});
- int[] locationOnScreen = new int[2];
- mActivityRule.runOnUiThread(() -> {
- mScrollViewStretch.getLocationOnScreen(locationOnScreen);
- });
-
- Bitmap[] bitmap = new Bitmap[1];
-
- int width = mScrollViewStretch.getWidth();
- int height = mScrollViewStretch.getHeight();
-
- CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
- locationOnScreen[0] + 89,
- locationOnScreen[1],
- -50,
- 0,
- 50,
- 5,
- new CtsTouchUtils.EventInjectionListener() {
- private int mNumEvents = 0;
- @Override
- public void onDownInjected(int xOnScreen, int yOnScreen) {
- }
-
- @Override
- public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
- mNumEvents++;
- if (mNumEvents == 5) {
- // Have to take the picture after drag, but before the up event
- bitmap[0] = takeScreenshot(locationOnScreen[0], locationOnScreen[1],
- width,
- height);
- }
- }
-
- @Override
- public void onUpInjected(int xOnScreen, int yOnScreen) {
- }
- });
-
- // The pink should stretch beyond its normal dimensions. Also avoid the shadow
- assertEquals(Color.MAGENTA, bitmap[0].getPixel(38, 49));
+ assertTrue(StretchEdgeUtil.dragLeftStretches(mActivityRule, mScrollViewStretch));
}
// If this test is showing as flaky, it is more likely that it is broken. I've
// leaned toward false positive over false negative.
@LargeTest
@Test
- public void testStretchRightAndCatch() throws Throwable {
+ public void testStretchAtRightAndCatch() throws Throwable {
// Make sure that the scroll view we care about is on screen and at the top:
showOnlyStretch();
@@ -990,86 +858,7 @@
mScrollViewStretch.scrollTo(210, 0);
});
- int[] locationOnScreen = new int[2];
- mActivityRule.runOnUiThread(() -> {
- mScrollViewStretch.getLocationOnScreen(locationOnScreen);
- });
-
- boolean[] wasDownDoneInTime = new boolean[1];
-
- // Try at most 5 times. Give slow devices their best chance to respond, but
- // don't penalize them with a flaky test if they can't complete in time.
- for (int i = 0; i < 5 && !wasDownDoneInTime[0]; i++) {
- CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
- locationOnScreen[0] + 89,
- locationOnScreen[1],
- -89,
- 0,
- 50,
- 5,
- new CtsTouchUtils.EventInjectionListener() {
- private long mLastAnimationTime;
-
- @Override
- public void onDownInjected(int xOnScreen, int yOnScreen) {
- }
-
- @Override
- public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
- mLastAnimationTime = AnimationUtils.currentAnimationTimeMillis();
- }
-
- @Override
- public void onUpInjected(int xOnScreen, int yOnScreen) {
- CtsTouchUtils.injectDownEvent(mInstrumentation.getUiAutomation(),
- SystemClock.uptimeMillis(), locationOnScreen[0],
- locationOnScreen[1], null);
- long animationTime = AnimationUtils.currentAnimationTimeMillis();
- // The receding time is 600 ms, but we don't want to be near the final
- // part of the animation when the pixels may overlap.
- if (animationTime - mLastAnimationTime < 400) {
- wasDownDoneInTime[0] = true;
- }
- }
- });
-
- // To avoid flaky tests, this ensures that the down event was received before the
- // recede went too far.
- if (wasDownDoneInTime[0]) {
- // Now make sure that we wait until the release should normally have finished:
- sleepAnimationTime(600);
-
- // Since we caught it, it should be held and still stretched.
- int width = mScrollViewStretch.getWidth();
- int height = mScrollViewStretch.getHeight();
-
- Bitmap bitmap = takeScreenshot(locationOnScreen[0], locationOnScreen[1], width,
- height);
-
- // The magenta should still be stretched
- assertEquals(Color.MAGENTA, bitmap.getPixel(38, 49));
- }
- CtsTouchUtils.injectUpEvent(mInstrumentation.getUiAutomation(),
- SystemClock.uptimeMillis(), false,
- locationOnScreen[0], locationOnScreen[1], null);
- }
- }
-
- /**
- * This sleeps until the {@link AnimationUtils#currentAnimationTimeMillis()} changes
- * by at least <code>durationMillis</code> milliseconds. This is useful for EdgeEffect because
- * it uses that mechanism to determine the animation duration.
- *
- * @param durationMillis The time to sleep in milliseconds.
- */
- private void sleepAnimationTime(long durationMillis) throws Exception {
- final long startTime = AnimationUtils.currentAnimationTimeMillis();
- long currentTime = startTime;
- final long endTime = startTime + durationMillis;
- do {
- Thread.sleep(endTime - currentTime);
- currentTime = AnimationUtils.currentAnimationTimeMillis();
- } while (currentTime < endTime);
+ assertTrue(StretchEdgeUtil.dragLeftTapAndHoldStretches(mActivityRule, mScrollViewStretch));
}
private void showOnlyStretch() throws Throwable {
@@ -1077,22 +866,14 @@
mScrollViewCustom.setVisibility(View.GONE);
mScrollViewCustomEmpty.setVisibility(View.GONE);
mScrollViewRegular.setVisibility(View.GONE);
+ // The stretch HorizontalScrollView is 90x90 pixels
+ Rect exclusionRect = new Rect(0, 0, 90, 90);
+ ArrayList exclusionRects = new ArrayList();
+ exclusionRects.add(exclusionRect);
+ mScrollViewStretch.setSystemGestureExclusionRects(exclusionRects);
});
}
- private Bitmap takeScreenshot(int screenPositionX, int screenPositionY, int width, int height) {
- SynchronousPixelCopy copy = new SynchronousPixelCopy();
- Bitmap dest = Bitmap.createBitmap(
- width, height,
- mActivity.getWindow().isWideColorGamut()
- ? Bitmap.Config.RGBA_F16 : Bitmap.Config.ARGB_8888);
- Rect srcRect = new Rect(0, 0, width, height);
- srcRect.offset(screenPositionX, screenPositionY);
- int copyResult = copy.request(mActivity.getWindow(), srcRect, dest);
- assertEquals(PixelCopy.SUCCESS, copyResult);
- return dest;
- }
-
private boolean isInRange(int current, int from, int to) {
if (from < to) {
return current >= from && current <= to;
diff --git a/tests/tests/widget/src/android/widget/cts/ListViewTest.java b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
index 548bce6..033c4ce 100644
--- a/tests/tests/widget/src/android/widget/cts/ListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
@@ -58,11 +58,16 @@
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.EdgeEffect;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.TextView;
+import android.widget.cts.util.StretchEdgeUtil;
import android.widget.cts.util.TestUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.LargeTest;
@@ -107,10 +112,14 @@
private final String[] mNameList = new String[] {
"Jacky", "David", "Kevin", "Michael", "Andy"
};
+ private final int[] mColorList = new int[] {
+ Color.BLUE, Color.CYAN, Color.GREEN, Color.YELLOW, Color.RED, Color.MAGENTA
+ };
private Instrumentation mInstrumentation;
private Activity mActivity;
private ListView mListView;
+ private ListView mListViewStretch;
private TextView mTextView;
private TextView mSecondTextView;
@@ -118,6 +127,7 @@
private ArrayAdapter<String> mAdapter_countries;
private ArrayAdapter<String> mAdapter_longCountries;
private ArrayAdapter<String> mAdapter_names;
+ private ColorAdapter mAdapterColors;
@Rule
public ActivityTestRule<ListViewCtsActivity> mActivityRule =
@@ -136,8 +146,10 @@
android.R.layout.simple_list_item_1, mLongCountryList);
mAdapter_names = new ArrayAdapter<>(mActivity, android.R.layout.simple_list_item_1,
mNameList);
+ mAdapterColors = new ColorAdapter(mActivity, mColorList);
mListView = (ListView) mActivity.findViewById(R.id.listview_default);
+ mListViewStretch = (ListView) mActivity.findViewById(R.id.listview_stretch);
}
@Test
@@ -1147,6 +1159,85 @@
Assert.assertEquals(tag, newItem.getTag());
}
+ @Test
+ public void testEdgeEffectType() {
+ // Should default to "glow"
+ assertEquals(EdgeEffect.TYPE_GLOW, mListView.getEdgeEffectType());
+
+ // This one has "stretch" attribute
+ assertEquals(EdgeEffect.TYPE_STRETCH, mListViewStretch.getEdgeEffectType());
+
+ mListViewStretch.setEdgeEffectType(EdgeEffect.TYPE_GLOW);
+ assertEquals(EdgeEffect.TYPE_GLOW, mListViewStretch.getEdgeEffectType());
+ mListViewStretch.setEdgeEffectType(EdgeEffect.TYPE_STRETCH);
+ assertEquals(EdgeEffect.TYPE_STRETCH, mListViewStretch.getEdgeEffectType());
+ }
+
+ @Test
+ public void testStretchAtTop() throws Throwable {
+ // Make sure that the view we care about is on screen and at the top:
+ showOnlyStretch();
+
+ assertTrue(StretchEdgeUtil.dragDownStretches(mActivityRule, mListViewStretch));
+ }
+
+ // If this test is showing as flaky, it is more likely that it is broken. I've
+ // leaned toward false positive over false negative.
+ @LargeTest
+ @Test
+ public void testStretchTopAndCatch() throws Throwable {
+ // Make sure that the view we care about is on screen and at the top:
+ showOnlyStretch();
+
+ assertTrue(StretchEdgeUtil.dragDownTapAndHoldStretches(mActivityRule, mListViewStretch));
+ }
+
+ private void scrollToBottomOfStretch() throws Throwable {
+ do {
+ mActivityRule.runOnUiThread(() -> {
+ mListViewStretch.scrollListBy(50);
+ });
+ } while (mListViewStretch.pointToPosition(0, 40) != mColorList.length - 1);
+ }
+
+ @Test
+ public void testStretchAtBottom() throws Throwable {
+ // Make sure that the view we care about is on screen and at the top:
+ showOnlyStretch();
+
+ scrollToBottomOfStretch();
+ assertTrue(StretchEdgeUtil.dragUpStretches(mActivityRule, mListViewStretch));
+ }
+
+ // If this test is showing as flaky, it is more likely that it is broken. I've
+ // leaned toward false positive over false negative.
+ @LargeTest
+ @Test
+ public void testStretchBottomAndCatch() throws Throwable {
+ // Make sure that the view we care about is on screen and at the top:
+ showOnlyStretch();
+
+ scrollToBottomOfStretch();
+ assertTrue(StretchEdgeUtil.dragUpTapAndHoldStretches(mActivityRule, mListViewStretch));
+ }
+
+ private void showOnlyStretch() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ ViewGroup parent = (ViewGroup) mListViewStretch.getParent();
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ View child = parent.getChildAt(i);
+ if (child != mListViewStretch) {
+ child.setVisibility(View.GONE);
+ }
+ }
+ mListViewStretch.setAdapter(mAdapterColors);
+ mListViewStretch.setDivider(null);
+ mListViewStretch.setDividerHeight(0);
+ });
+ // Give it an opportunity to finish layout.
+ mActivityRule.runOnUiThread(() -> {});
+ }
+
private static class StableArrayAdapter<T> extends ArrayAdapter<T> {
public StableArrayAdapter(Context context, int resource, List<T> objects) {
super(context, resource, objects);
@@ -1295,4 +1386,43 @@
verify(overscrollFooterDrawable, atLeastOnce()).draw(any(Canvas.class));
}
+
+ private static class ColorAdapter extends BaseAdapter {
+ private int[] mColors;
+ private Context mContext;
+
+ ColorAdapter(Context context, int[] colors) {
+ mContext = context;
+ mColors = colors;
+ }
+
+ @Override
+ public int getCount() {
+ return mColors.length;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mColors[position];
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ int color = mColors[position];
+ if (convertView != null) {
+ convertView.setBackgroundColor(color);
+ return convertView;
+ }
+ View view = new View(mContext);
+ view.setBackgroundColor(color);
+ view.setLayoutParams(new ViewGroup.LayoutParams(90, 50));
+ return view;
+ }
+ }
}
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index d7d7438..bf96577 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -18,6 +18,16 @@
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
+import static android.widget.RemoteViews.MARGIN_BOTTOM;
+import static android.widget.RemoteViews.MARGIN_END;
+import static android.widget.RemoteViews.MARGIN_LEFT;
+import static android.widget.RemoteViews.MARGIN_RIGHT;
+import static android.widget.RemoteViews.MARGIN_START;
+import static android.widget.RemoteViews.MARGIN_TOP;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -30,11 +40,15 @@
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.BlendMode;
+import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
@@ -50,6 +64,7 @@
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Chronometer;
+import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.FrameLayout;
@@ -82,6 +97,7 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ThrowingRunnable;
import com.android.compatibility.common.util.WidgetTestUtils;
import org.junit.Before;
@@ -250,6 +266,27 @@
}
@Test
+ public void testSetIcon_nightMode() throws Throwable {
+ ImageView image = (ImageView) mResult.findViewById(R.id.remoteView_image);
+ Icon iconLight = Icon.createWithResource(mContext, R.drawable.icon_green);
+ Icon iconDark = Icon.createWithResource(mContext, R.drawable.icon_blue);
+ mRemoteViews.setIcon(R.id.remoteView_image, "setImageIcon", iconLight, iconDark);
+
+ applyNightModeThenTest(false, () -> {
+ assertNotNull(image.getDrawable());
+ BitmapDrawable dLight = (BitmapDrawable) mContext.getDrawable(R.drawable.icon_green);
+ WidgetTestUtils.assertEquals(dLight.getBitmap(),
+ ((BitmapDrawable) image.getDrawable()).getBitmap());
+ });
+ applyNightModeThenTest(true, () -> {
+ assertNotNull(image.getDrawable());
+ BitmapDrawable dDark = (BitmapDrawable) mContext.getDrawable(R.drawable.icon_blue);
+ WidgetTestUtils.assertEquals(dDark.getBitmap(),
+ ((BitmapDrawable) image.getDrawable()).getBitmap());
+ });
+ }
+
+ @Test
public void testSetImageViewIcon() throws Throwable {
ImageView image = (ImageView) mResult.findViewById(R.id.remoteView_image);
assertNull(image.getDrawable());
@@ -719,6 +756,48 @@
}
@Test
+ public void testSetOnCheckedChangePendingIntent() throws Throwable {
+ String action = "my-checked-change-action";
+ MockBroadcastReceiver receiver = new MockBroadcastReceiver();
+ mContext.registerReceiver(receiver, new IntentFilter(action));
+
+ Intent intent = new Intent(action).setPackage(mContext.getPackageName());
+ PendingIntent pendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mRemoteViews.setOnCheckedChangeResponse(R.id.remoteView_checkBox,
+ RemoteViews.RemoteResponse.fromPendingIntent(pendingIntent));
+
+ // View being checked to true should launch the intent with the extra set to true.
+ CompoundButton view = mResult.findViewById(R.id.remoteView_checkBox);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ mActivityRule.runOnUiThread(() -> view.setChecked(true));
+ mInstrumentation.waitForIdleSync();
+ assertNotNull(receiver.mIntent);
+ assertTrue(receiver.mIntent.getBooleanExtra(RemoteViews.EXTRA_CHECKED, false));
+
+ // Changing the checked state from a RemoteViews action should not launch the intent.
+ receiver.mIntent = null;
+ mRemoteViews.setCompoundButtonChecked(R.id.remoteView_checkBox, false);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ mInstrumentation.waitForIdleSync();
+ assertFalse(view.isChecked());
+ assertNull(receiver.mIntent);
+
+ // View being checked to false should launch the intent with the extra set to false.
+ receiver.mIntent = null;
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ mActivityRule.runOnUiThread(() -> view.setChecked(true));
+ mActivityRule.runOnUiThread(() -> view.setChecked(false));
+ mInstrumentation.waitForIdleSync();
+ assertNotNull(receiver.mIntent);
+ assertFalse(receiver.mIntent.getBooleanExtra(RemoteViews.EXTRA_CHECKED, true));
+ }
+
+ @Test
public void testSetLong() throws Throwable {
long base1 = 50;
long base2 = -50;
@@ -849,6 +928,23 @@
}
@Test
+ public void testSetBlendMode() throws Throwable {
+ ImageView imageView = mResult.findViewById(R.id.remoteView_image);
+
+ mRemoteViews.setBlendMode(R.id.remoteView_image, "setImageTintBlendMode", BlendMode.PLUS);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(BlendMode.PLUS, imageView.getImageTintBlendMode());
+
+ mRemoteViews.setBlendMode(R.id.remoteView_image, "setImageTintBlendMode", BlendMode.SRC_IN);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(BlendMode.SRC_IN, imageView.getImageTintBlendMode());
+
+ mRemoteViews.setBlendMode(R.id.remoteView_image, "setImageTintBlendMode", null);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertNull(imageView.getImageTintBlendMode());
+ }
+
+ @Test
public void testRemoveAllViews() throws Throwable {
ViewGroup root = (ViewGroup) mResult.findViewById(R.id.remoteViews_good);
assertTrue(root.getChildCount() > 0);
@@ -932,6 +1028,182 @@
}
@Test
+ public void testSetViewLayoutMargin() throws Throwable {
+ View textView = mResult.findViewById(R.id.remoteView_text);
+
+ mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_LEFT, 10, COMPLEX_UNIT_PX);
+ mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_TOP, 20, COMPLEX_UNIT_PX);
+ mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_RIGHT, 30, COMPLEX_UNIT_PX);
+ mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_BOTTOM, 40, COMPLEX_UNIT_PX);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertMargins(textView, 10, 20, 30, 40);
+
+ mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_LEFT, 10, COMPLEX_UNIT_DIP);
+ mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_TOP, 20, COMPLEX_UNIT_DIP);
+ mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_RIGHT, 30, COMPLEX_UNIT_DIP);
+ mRemoteViews.setViewLayoutMargin(R.id.remoteView_text, MARGIN_BOTTOM, 40, COMPLEX_UNIT_DIP);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ DisplayMetrics displayMetrics = textView.getResources().getDisplayMetrics();
+ assertMargins(
+ textView,
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 10, displayMetrics)),
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 20, displayMetrics)),
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 30, displayMetrics)),
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 40, displayMetrics)));
+ }
+
+ @Test
+ public void testSetViewLayoutMargin_layoutDirection() throws Throwable {
+ View textViewLtr = mResult.findViewById(R.id.remoteView_text_ltr);
+ mRemoteViews.setViewLayoutMargin(textViewLtr.getId(), MARGIN_START, 10, COMPLEX_UNIT_DIP);
+ mRemoteViews.setViewLayoutMargin(textViewLtr.getId(), MARGIN_TOP, 20, COMPLEX_UNIT_DIP);
+ mRemoteViews.setViewLayoutMargin(textViewLtr.getId(), MARGIN_END, 30, COMPLEX_UNIT_DIP);
+ mRemoteViews.setViewLayoutMargin(textViewLtr.getId(), MARGIN_BOTTOM, 40, COMPLEX_UNIT_DIP);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ DisplayMetrics displayMetrics = textViewLtr.getResources().getDisplayMetrics();
+ assertMargins(
+ textViewLtr,
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 10, displayMetrics)),
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 20, displayMetrics)),
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 30, displayMetrics)),
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 40, displayMetrics)));
+
+ View textViewRtl = mResult.findViewById(R.id.remoteView_text_rtl);
+ mRemoteViews.setViewLayoutMargin(textViewRtl.getId(), MARGIN_START, 10, COMPLEX_UNIT_DIP);
+ mRemoteViews.setViewLayoutMargin(textViewRtl.getId(), MARGIN_TOP, 20, COMPLEX_UNIT_DIP);
+ mRemoteViews.setViewLayoutMargin(textViewRtl.getId(), MARGIN_END, 30, COMPLEX_UNIT_DIP);
+ mRemoteViews.setViewLayoutMargin(textViewRtl.getId(), MARGIN_BOTTOM, 40, COMPLEX_UNIT_DIP);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ displayMetrics = textViewRtl.getResources().getDisplayMetrics();
+ assertMargins(
+ textViewRtl,
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 30, displayMetrics)),
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 20, displayMetrics)),
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 10, displayMetrics)),
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 40, displayMetrics)));
+ }
+
+ @Test
+ public void testSetViewLayoutMarginDimen() throws Throwable {
+ View textView = mResult.findViewById(R.id.remoteView_text);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text, MARGIN_LEFT, R.dimen.textview_padding_left);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text, MARGIN_TOP, R.dimen.textview_padding_top);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text, MARGIN_RIGHT, R.dimen.textview_padding_right);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text, MARGIN_BOTTOM, R.dimen.textview_padding_bottom);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertMargins(
+ textView,
+ textView.getResources().getDimensionPixelSize(R.dimen.textview_padding_left),
+ textView.getResources().getDimensionPixelSize(R.dimen.textview_padding_top),
+ textView.getResources().getDimensionPixelSize(R.dimen.textview_padding_right),
+ textView.getResources().getDimensionPixelSize(R.dimen.textview_padding_bottom));
+ }
+
+ @Test
+ public void testSetViewLayoutMarginDimen_layoutDirection() throws Throwable {
+ View textViewLtr = mResult.findViewById(R.id.remoteView_text_ltr);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text_ltr, MARGIN_START, R.dimen.textview_padding_left);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text_ltr, MARGIN_TOP, R.dimen.textview_padding_top);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text_ltr, MARGIN_END, R.dimen.textview_padding_right);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text_ltr, MARGIN_BOTTOM, R.dimen.textview_padding_bottom);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertMargins(
+ textViewLtr,
+ textViewLtr.getResources().getDimensionPixelSize(R.dimen.textview_padding_left),
+ textViewLtr.getResources().getDimensionPixelSize(R.dimen.textview_padding_top),
+ textViewLtr.getResources().getDimensionPixelSize(R.dimen.textview_padding_right),
+ textViewLtr.getResources().getDimensionPixelSize(R.dimen.textview_padding_bottom));
+
+ View textViewRtl = mResult.findViewById(R.id.remoteView_text_rtl);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text_rtl, MARGIN_START, R.dimen.textview_padding_left);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text_rtl, MARGIN_TOP, R.dimen.textview_padding_top);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text_rtl, MARGIN_END, R.dimen.textview_padding_right);
+ mRemoteViews.setViewLayoutMarginDimen(
+ R.id.remoteView_text_rtl, MARGIN_BOTTOM, R.dimen.textview_padding_bottom);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertMargins(
+ textViewRtl,
+ textViewRtl.getResources().getDimensionPixelSize(R.dimen.textview_padding_right),
+ textViewRtl.getResources().getDimensionPixelSize(R.dimen.textview_padding_top),
+ textViewRtl.getResources().getDimensionPixelSize(R.dimen.textview_padding_left),
+ textViewRtl.getResources().getDimensionPixelSize(R.dimen.textview_padding_bottom));
+
+ }
+
+ @Test
+ public void testSetViewLayoutWidth() throws Throwable {
+ View textView = mResult.findViewById(R.id.remoteView_text);
+ DisplayMetrics displayMetrics = textView.getResources().getDisplayMetrics();
+
+ mRemoteViews.setViewLayoutWidth(R.id.remoteView_text, 10, COMPLEX_UNIT_PX);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(10, textView.getLayoutParams().width);
+
+ mRemoteViews.setViewLayoutWidth(R.id.remoteView_text, 20, COMPLEX_UNIT_DIP);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 20, displayMetrics)),
+ textView.getLayoutParams().width);
+
+ mRemoteViews.setViewLayoutWidth(
+ R.id.remoteView_text, ViewGroup.LayoutParams.MATCH_PARENT, COMPLEX_UNIT_PX);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, textView.getLayoutParams().width);
+ }
+
+ @Test
+ public void testSetViewLayoutWidthDimen() throws Throwable {
+ View textView = mResult.findViewById(R.id.remoteView_text);
+ mRemoteViews.setViewLayoutWidthDimen(R.id.remoteView_text, R.dimen.textview_fixed_width);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(
+ textView.getResources().getDimensionPixelSize(R.dimen.textview_fixed_width),
+ textView.getLayoutParams().width);
+ }
+
+ @Test
+ public void testSetViewLayoutHeight() throws Throwable {
+ View textView = mResult.findViewById(R.id.remoteView_text);
+ DisplayMetrics displayMetrics = textView.getResources().getDisplayMetrics();
+
+ mRemoteViews.setViewLayoutHeight(R.id.remoteView_text, 10, COMPLEX_UNIT_PX);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(10, textView.getLayoutParams().height);
+
+ mRemoteViews.setViewLayoutHeight(R.id.remoteView_text, 20, COMPLEX_UNIT_DIP);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(
+ Math.round(TypedValue.applyDimension(COMPLEX_UNIT_DIP, 20, displayMetrics)),
+ textView.getLayoutParams().height);
+
+ mRemoteViews.setViewLayoutHeight(
+ R.id.remoteView_text, ViewGroup.LayoutParams.MATCH_PARENT, COMPLEX_UNIT_PX);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, textView.getLayoutParams().height);
+ }
+
+ @Test
+ public void testSetViewLayoutHeightDimen() throws Throwable {
+ View textView = mResult.findViewById(R.id.remoteView_text);
+ mRemoteViews.setViewLayoutHeightDimen(R.id.remoteView_text, R.dimen.textview_fixed_height);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(
+ textView.getResources().getDimensionPixelSize(R.dimen.textview_fixed_height),
+ textView.getLayoutParams().height);
+ }
+
+ @Test
public void testSetIntDimen_fromResources() throws Throwable {
TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
int expectedValue = mContext.getResources().getDimensionPixelSize(R.dimen.popup_row_height);
@@ -1037,7 +1309,6 @@
mRemoteViews.reapply(mContext, mResult);
}
-
@Test
public void testSetColor() throws Throwable {
TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
@@ -1053,6 +1324,45 @@
}
@Test
+ public void testSetColorStateList() throws Throwable {
+ ProgressBar progressBar = mResult.findViewById(R.id.remoteView_progress);
+
+ ColorStateList tintList = new ColorStateList(
+ new int[][] {{android.R.attr.state_checked}, {}},
+ new int[] {Color.BLACK, Color.WHITE});
+ mRemoteViews.setColorStateList(R.id.remoteView_progress, "setProgressTintList", tintList);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(tintList, progressBar.getProgressTintList());
+
+ mRemoteViews.setColorStateList(R.id.remoteView_progress, "setProgressTintList", null);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertNull(progressBar.getProgressTintList());
+
+ TextView textView = mResult.findViewById(R.id.remoteView_text);
+ mRemoteViews.setColorStateList(R.id.remoteView_text, "setTextColor", tintList);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(tintList, textView.getTextColors());
+
+ ColorStateList solid = ColorStateList.valueOf(Color.RED);
+ mRemoteViews.setColorStateList(R.id.remoteView_text, "setBackgroundTintList", solid);
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ assertEquals(solid, textView.getBackgroundTintList());
+ }
+
+ @Test
+ public void testSetColorStateInt_nightMode() throws Throwable {
+ TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
+ mRemoteViews.setColorInt(R.id.remoteView_text, "setTextColor", Color.BLACK, Color.WHITE);
+
+ applyNightModeThenTest(
+ false,
+ () -> assertEquals(ColorStateList.valueOf(Color.BLACK), textView.getTextColors()));
+ applyNightModeThenTest(
+ true,
+ () -> assertEquals(ColorStateList.valueOf(Color.WHITE), textView.getTextColors()));
+ }
+
+ @Test
public void testSetColorStateList_fromResources() throws Throwable {
TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
ColorStateList expectedValue = mContext.getColorStateList(R.color.testcolorstatelist1);
@@ -1076,6 +1386,17 @@
}
@Test
+ public void testSetColorStateList_nightMode() throws Throwable {
+ TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
+ ColorStateList lightMode = ColorStateList.valueOf(Color.BLACK);
+ ColorStateList darkMode = ColorStateList.valueOf(Color.WHITE);
+ mRemoteViews.setColorStateList(R.id.remoteView_text, "setTextColor", lightMode, darkMode);
+
+ applyNightModeThenTest(false, () -> assertEquals(lightMode, textView.getTextColors()));
+ applyNightModeThenTest(true, () -> assertEquals(darkMode, textView.getTextColors()));
+ }
+
+ @Test
public void testSetViewOutlinePreferredRadius() throws Throwable {
View root = mResult.findViewById(R.id.remoteViews_good);
DisplayMetrics displayMetrics = root.getResources().getDisplayMetrics();
@@ -1189,4 +1510,49 @@
}
}
}
+
+ /**
+ * Sets the night mode, reapplies the remote views, runs test, and then restores the previous
+ * night mode.
+ */
+ private void applyNightModeThenTest(
+ boolean nightMode, ThrowingRunnable test) throws Throwable {
+ final String nightModeText = runShellCommand("cmd uimode night");
+ final String[] nightModeSplit = nightModeText.split(":");
+ if (nightModeSplit.length != 2) {
+ fail("Failed to get initial night mode value from " + nightModeText);
+ }
+ final String initialNightMode = nightModeSplit[1].trim();
+
+ try {
+ runShellCommand("cmd uimode night " + (nightMode ? "yes" : "no"));
+ mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+ test.run();
+ } finally {
+ runShellCommand("cmd uimode night " + initialNightMode);
+ }
+ }
+
+ private static void assertMargins(View view, int left, int top, int right, int bottom) {
+ ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
+ if (!(layoutParams instanceof ViewGroup.MarginLayoutParams)) {
+ fail("View doesn't have MarginLayoutParams");
+ }
+
+ ViewGroup.MarginLayoutParams margins = (ViewGroup.MarginLayoutParams) layoutParams;
+ assertEquals("[left margin]", left, margins.leftMargin);
+ assertEquals("[top margin]", top, margins.topMargin);
+ assertEquals("[right margin]", right, margins.rightMargin);
+ assertEquals("[bottom margin]", bottom, margins.bottomMargin);
+ }
+
+ private static final class MockBroadcastReceiver extends BroadcastReceiver {
+
+ Intent mIntent;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mIntent = intent;
+ }
+ }
}
diff --git a/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
index e88e992..93bd02e 100644
--- a/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
@@ -28,21 +28,18 @@
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
-import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Xml;
-import android.view.PixelCopy;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
import android.widget.EdgeEffect;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import android.widget.TextView;
+import android.widget.cts.util.StretchEdgeUtil;
import android.widget.cts.util.TestUtils;
import androidx.test.InstrumentationRegistry;
@@ -52,9 +49,7 @@
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.CtsTouchUtils;
import com.android.compatibility.common.util.PollingCheck;
-import com.android.compatibility.common.util.SynchronousPixelCopy;
import org.junit.Before;
import org.junit.Rule;
@@ -849,125 +844,36 @@
}
@Test
+ public void testEdgeEffectType() {
+ // Should default to "glow"
+ assertEquals(EdgeEffect.TYPE_GLOW, mScrollViewRegular.getEdgeEffectType());
+
+ // This one has "stretch" attribute
+ assertEquals(EdgeEffect.TYPE_STRETCH, mScrollViewStretch.getEdgeEffectType());
+
+ mScrollViewStretch.setEdgeEffectType(EdgeEffect.TYPE_GLOW);
+ assertEquals(EdgeEffect.TYPE_GLOW, mScrollViewStretch.getEdgeEffectType());
+ mScrollViewStretch.setEdgeEffectType(EdgeEffect.TYPE_STRETCH);
+ assertEquals(EdgeEffect.TYPE_STRETCH, mScrollViewStretch.getEdgeEffectType());
+ }
+
+ @Test
public void testStretchAtTop() throws Throwable {
// Make sure that the scroll view we care about is on screen and at the top:
showOnlyStretch();
- int[] locationOnScreen = new int[2];
- mActivityRule.runOnUiThread(() -> {
- mScrollViewStretch.getLocationOnScreen(locationOnScreen);
- });
-
- Bitmap[] bitmap = new Bitmap[1];
-
- int width = mScrollViewStretch.getWidth();
- int height = mScrollViewStretch.getHeight();
-
- CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
- locationOnScreen[0],
- locationOnScreen[1],
- 0,
- 50,
- 50,
- 5,
- new CtsTouchUtils.EventInjectionListener() {
- private int mNumEvents = 0;
-
- @Override
- public void onDownInjected(int xOnScreen, int yOnScreen) {
- }
-
- @Override
- public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
- mNumEvents++;
- if (mNumEvents == 5) {
- // Have to take the picture after drag, but before the up event
- bitmap[0] = takeScreenshot(locationOnScreen[0], locationOnScreen[1],
- width,
- height);
- }
- }
-
- @Override
- public void onUpInjected(int xOnScreen, int yOnScreen) {
- }
- });
-
- // The blue should stretch beyond its normal dimensions
- assertEquals(Color.BLUE, bitmap[0].getPixel(1, 52));
+ assertTrue(StretchEdgeUtil.dragDownStretches(mActivityRule, mScrollViewStretch));
}
// If this test is showing as flaky, it is more likely that it is broken. I've
// leaned toward false positive over false negative.
@LargeTest
@Test
- public void testStretchTopAndCatch() throws Throwable {
+ public void testStretchAtTopAndCatch() throws Throwable {
// Make sure that the scroll view we care about is on screen and at the top:
showOnlyStretch();
- int[] locationOnScreen = new int[2];
- mActivityRule.runOnUiThread(() -> {
- mScrollViewStretch.getLocationOnScreen(locationOnScreen);
- });
-
- boolean[] wasDownDoneInTime = new boolean[1];
-
- // Try at most 5 times. Give slow devices their best chance to respond, but
- // don't penalize them with a flaky test if they can't complete in time.
- for (int i = 0; i < 5 && !wasDownDoneInTime[0]; i++) {
- CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
- locationOnScreen[0],
- locationOnScreen[1],
- 0,
- 89,
- 50,
- 5,
- new CtsTouchUtils.EventInjectionListener() {
- private long mLastAnimationTime;
-
- @Override
- public void onDownInjected(int xOnScreen, int yOnScreen) {
- }
-
- @Override
- public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
- mLastAnimationTime = AnimationUtils.currentAnimationTimeMillis();
- }
-
- @Override
- public void onUpInjected(int xOnScreen, int yOnScreen) {
- CtsTouchUtils.injectDownEvent(mInstrumentation.getUiAutomation(),
- SystemClock.uptimeMillis(), locationOnScreen[0],
- locationOnScreen[1], null);
- long animationTime = AnimationUtils.currentAnimationTimeMillis();
- // The receding time is 600 ms, but we don't want to be near the final
- // part of the animation when the pixels may overlap.
- if (animationTime - mLastAnimationTime < 400) {
- wasDownDoneInTime[0] = true;
- }
- }
- });
-
- // To avoid flaky tests, this ensures that the down event was received before the
- // recede went too far.
- if (wasDownDoneInTime[0]) {
- // Now make sure that we wait until the release should normally have finished:
- sleepAnimationTime(600);
-
- // Since we caught it, it should be held and still stretched.
- int width = mScrollViewStretch.getWidth();
- int height = mScrollViewStretch.getHeight();
-
- Bitmap bitmap = takeScreenshot(locationOnScreen[0], locationOnScreen[1], width,
- height);
-
- // The blue should still be stretched
- assertEquals(Color.BLUE, bitmap.getPixel(1, 52));
- }
- CtsTouchUtils.injectUpEvent(mInstrumentation.getUiAutomation(),
- SystemClock.uptimeMillis(), false,
- locationOnScreen[0], locationOnScreen[1], null);
- }
+ assertTrue(StretchEdgeUtil.dragDownTapAndHoldStretches(mActivityRule, mScrollViewStretch));
}
@Test
@@ -980,54 +886,14 @@
mScrollViewStretch.scrollTo(0, 210);
});
- int[] locationOnScreen = new int[2];
- mActivityRule.runOnUiThread(() -> {
- mScrollViewStretch.getLocationOnScreen(locationOnScreen);
- });
-
- Bitmap[] bitmap = new Bitmap[1];
-
- int width = mScrollViewStretch.getWidth();
- int height = mScrollViewStretch.getHeight();
-
- CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
- locationOnScreen[0],
- locationOnScreen[1] + 89,
- 0,
- -50,
- 50,
- 5,
- new CtsTouchUtils.EventInjectionListener() {
- private int mNumEvents = 0;
- @Override
- public void onDownInjected(int xOnScreen, int yOnScreen) {
- }
-
- @Override
- public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
- mNumEvents++;
- if (mNumEvents == 5) {
- // Have to take the picture after drag, but before the up event
- bitmap[0] = takeScreenshot(locationOnScreen[0], locationOnScreen[1],
- width,
- height);
- }
- }
-
- @Override
- public void onUpInjected(int xOnScreen, int yOnScreen) {
- }
- });
-
- // The magenta should stretch beyond its normal dimensions
- assertEquals(Color.MAGENTA, bitmap[0].getPixel(1, 38));
+ assertTrue(StretchEdgeUtil.dragUpStretches(mActivityRule, mScrollViewStretch));
}
// If this test is showing as flaky, it is more likely that it is broken. I've
// leaned toward false positive over false negative.
@LargeTest
@Test
- public void testStretchBottomAndCatch() throws Throwable {
+ public void testStretchAtBottomAndCatch() throws Throwable {
// Make sure that the scroll view we care about is on screen and at the top:
showOnlyStretch();
@@ -1036,86 +902,7 @@
mScrollViewStretch.scrollTo(0, 210);
});
- int[] locationOnScreen = new int[2];
- mActivityRule.runOnUiThread(() -> {
- mScrollViewStretch.getLocationOnScreen(locationOnScreen);
- });
-
- boolean[] wasDownDoneInTime = new boolean[1];
-
- // Try at most 5 times. Give slow devices their best chance to respond, but
- // don't penalize them with a flaky test if they can't complete in time.
- for (int i = 0; i < 5 && !wasDownDoneInTime[0]; i++) {
- CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule,
- locationOnScreen[0],
- locationOnScreen[1] + 89,
- 0,
- -89,
- 50,
- 5,
- new CtsTouchUtils.EventInjectionListener() {
- private long mLastAnimationTime;
-
- @Override
- public void onDownInjected(int xOnScreen, int yOnScreen) {
- }
-
- @Override
- public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
- mLastAnimationTime = AnimationUtils.currentAnimationTimeMillis();
- }
-
- @Override
- public void onUpInjected(int xOnScreen, int yOnScreen) {
- CtsTouchUtils.injectDownEvent(mInstrumentation.getUiAutomation(),
- SystemClock.uptimeMillis(), locationOnScreen[0],
- locationOnScreen[1], null);
- long animationTime = AnimationUtils.currentAnimationTimeMillis();
- // The receding time is 600 ms, but we don't want to be near the final
- // part of the animation when the pixels may overlap.
- if (animationTime - mLastAnimationTime < 400) {
- wasDownDoneInTime[0] = true;
- }
- }
- });
-
- // To avoid flaky tests, this ensures that the down event was received before the
- // recede went too far.
- if (wasDownDoneInTime[0]) {
- // Now make sure that we wait until the release should normally have finished:
- sleepAnimationTime(600);
-
- // Since we caught it, it should be held and still stretched.
- int width = mScrollViewStretch.getWidth();
- int height = mScrollViewStretch.getHeight();
-
- Bitmap bitmap = takeScreenshot(locationOnScreen[0], locationOnScreen[1], width,
- height);
-
- // The magenta should still be stretched
- assertEquals(Color.MAGENTA, bitmap.getPixel(1, 38));
- }
- CtsTouchUtils.injectUpEvent(mInstrumentation.getUiAutomation(),
- SystemClock.uptimeMillis(), false,
- locationOnScreen[0], locationOnScreen[1], null);
- }
- }
-
- /**
- * This sleeps until the {@link AnimationUtils#currentAnimationTimeMillis()} changes
- * by at least <code>durationMillis</code> milliseconds. This is useful for EdgeEffect because
- * it uses that mechanism to determine the animation duration.
- *
- * @param durationMillis The time to sleep in milliseconds.
- */
- private void sleepAnimationTime(long durationMillis) throws Exception {
- final long startTime = AnimationUtils.currentAnimationTimeMillis();
- long currentTime = startTime;
- final long endTime = startTime + durationMillis;
- do {
- Thread.sleep(endTime - currentTime);
- currentTime = AnimationUtils.currentAnimationTimeMillis();
- } while (currentTime < endTime);
+ assertTrue(StretchEdgeUtil.dragUpTapAndHoldStretches(mActivityRule, mScrollViewStretch));
}
private void showOnlyStretch() throws Throwable {
@@ -1126,19 +913,6 @@
});
}
- private Bitmap takeScreenshot(int screenPositionX, int screenPositionY, int width, int height) {
- SynchronousPixelCopy copy = new SynchronousPixelCopy();
- Bitmap dest = Bitmap.createBitmap(
- width, height,
- mActivity.getWindow().isWideColorGamut()
- ? Bitmap.Config.RGBA_F16 : Bitmap.Config.ARGB_8888);
- Rect srcRect = new Rect(0, 0, width, height);
- srcRect.offset(screenPositionX, screenPositionY);
- int copyResult = copy.request(mActivity.getWindow(), srcRect, dest);
- assertEquals(PixelCopy.SUCCESS, copyResult);
- return dest;
- }
-
private boolean isInRange(int current, int from, int to) {
if (from < to) {
return current >= from && current <= to;
diff --git a/tests/tests/widget/src/android/widget/cts/ToastPresenterTest.java b/tests/tests/widget/src/android/widget/cts/ToastPresenterTest.java
new file mode 100644
index 0000000..70f03a70
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/ToastPresenterTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.INotificationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.ServiceManager;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.IAccessibilityManager;
+import android.widget.FrameLayout;
+import android.widget.ToastPresenter;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ToastPresenterTest {
+ private static final String PACKAGE_NAME = "pkg";
+
+ private Context mContext;
+ private ToastPresenter mToastPresenter;
+
+ @Before
+ public void setup() {
+ mContext = getInstrumentation().getContext();
+
+ mToastPresenter = new ToastPresenter(
+ mContext,
+ IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)),
+ INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
+ PACKAGE_NAME);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testUpdateLayoutParams() {
+ View view = new FrameLayout(mContext);
+ Binder token = new Binder();
+ Binder windowToken = new Binder();
+ mToastPresenter.show(view, token, windowToken, 0, 0, 0, 0, 0, 0, null);
+ mToastPresenter.updateLayoutParams(1, 2, 3, 4, 0);
+
+ WindowManager.LayoutParams lp = (WindowManager.LayoutParams) view.getLayoutParams();
+ assertEquals(1, lp.x);
+ assertEquals(2, lp.y);
+ assertEquals(3, (int) lp.horizontalMargin);
+ assertEquals(4, (int) lp.verticalMargin);
+ }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
new file mode 100644
index 0000000..8520f44
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("StretchEdgeUtil")
+
+package android.widget.cts.util
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.SystemClock
+import android.view.PixelCopy
+import android.view.View
+import android.view.Window
+import android.view.animation.AnimationUtils
+import androidx.test.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.android.compatibility.common.util.CtsTouchUtils
+import com.android.compatibility.common.util.CtsTouchUtils.EventInjectionListener
+import com.android.compatibility.common.util.SynchronousPixelCopy
+import org.junit.Assert
+
+/* ---------------------------------------------------------------------------
+ * This file contains utility functions for testing the overscroll stretch
+ * effect. Containers are 90 x 90 pixels and contains colored rectangles
+ * that are 90 x 50 pixels (or 50 x 90 pixels for horizontal containers).
+ *
+ * The first rectangle must be Color.BLUE and the last rectangle must be
+ * Color.MAGENTA.
+ * ---------------------------------------------------------------------------
+ */
+
+/**
+ * This sleeps until the [AnimationUtils.currentAnimationTimeMillis] changes
+ * by at least `durationMillis` milliseconds. This is useful for EdgeEffect because
+ * it uses that mechanism to determine the animation duration.
+ *
+ * @param durationMillis The time to sleep in milliseconds.
+ */
+private fun sleepAnimationTime(durationMillis: Long) {
+ val startTime = AnimationUtils.currentAnimationTimeMillis()
+ var currentTime = startTime
+ val endTime = startTime + durationMillis
+ do {
+ Thread.sleep(endTime - currentTime)
+ currentTime = AnimationUtils.currentAnimationTimeMillis()
+ } while (currentTime < endTime)
+}
+
+/**
+ * Takes a screen shot at the given coordinates and returns the Bitmap.
+ */
+private fun takeScreenshot(
+ window: Window,
+ screenPositionX: Int,
+ screenPositionY: Int,
+ width: Int,
+ height: Int
+): Bitmap {
+ val copy = SynchronousPixelCopy()
+ val dest = Bitmap.createBitmap(
+ width, height,
+ if (window.isWideColorGamut()) Bitmap.Config.RGBA_F16 else Bitmap.Config.ARGB_8888)
+ val srcRect = Rect(0, 0, width, height)
+ srcRect.offset(screenPositionX, screenPositionY)
+ val copyResult: Int = copy.request(window, srcRect, dest)
+ Assert.assertEquals(PixelCopy.SUCCESS.toLong(), copyResult.toLong())
+ return dest
+}
+
+/**
+ * Drags an area of the screen and executes [onFinalMove] after sending the final drag
+ * motion and [onUp] after the drag up event has been sent.
+ */
+private fun dragAndExecute(
+ activityRule: ActivityTestRule<*>,
+ screenX: Int,
+ screenY: Int,
+ deltaX: Int,
+ deltaY: Int,
+ onFinalMove: () -> Unit = {},
+ onUp: () -> Unit = {}
+) {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ CtsTouchUtils.emulateDragGesture(instrumentation, activityRule,
+ screenX,
+ screenY,
+ deltaX,
+ deltaY,
+ 50,
+ 10,
+ object : EventInjectionListener {
+ private var mNumEvents = 0
+ override fun onDownInjected(xOnScreen: Int, yOnScreen: Int) {}
+ override fun onMoveInjected(xOnScreen: IntArray, yOnScreen: IntArray) {
+ mNumEvents++
+ if (mNumEvents == 10) {
+ onFinalMove()
+ }
+ }
+
+ override fun onUpInjected(xOnScreen: Int, yOnScreen: Int) {
+ onUp()
+ }
+ })
+}
+
+/**
+ * Drags inside [view] starting at coordinates ([viewX], [viewY]) relative to [view] and moving
+ * ([deltaX], [deltaY]) pixels before lifting. A Bitmap is captured after the final drag event,
+ * before the up event.
+ * @return A Bitmap of [view] after the final drag motion event.
+ */
+private fun dragAndCapture(
+ activityRule: ActivityTestRule<*>,
+ view: View,
+ viewX: Int,
+ viewY: Int,
+ deltaX: Int,
+ deltaY: Int
+): Bitmap {
+ var bitmap: Bitmap? = null
+ val locationOnScreen = IntArray(2)
+ activityRule.runOnUiThread {
+ view.getLocationOnScreen(locationOnScreen)
+ }
+
+ val screenX = locationOnScreen[0]
+ val screenY = locationOnScreen[1]
+
+ dragAndExecute(
+ activityRule = activityRule,
+ screenX = screenX + viewX,
+ screenY = screenY + viewY,
+ deltaX = deltaX,
+ deltaY = deltaY,
+ onFinalMove = {
+ bitmap = takeScreenshot(
+ activityRule.activity.window,
+ screenX,
+ screenY,
+ view.width,
+ view.height
+ )
+ }
+ )
+ return bitmap!!
+}
+
+/**
+ * Drags in [view], starting at coordinates ([viewX], [viewY]) relative to [view] and moving
+ * ([deltaX], [deltaY]) pixels before lifting. Immediately after the up event, a down event
+ * is sent. If it happens within 400 milliseconds of the last motion event, the Bitmap is captured
+ * after 600ms more. If an animation was going to run, this allows that animation to finish before
+ * capturing the Bitmap. This is attempted up to 5 times.
+ *
+ * @return A Bitmap of [view] after the drag, release, then tap and hold, or `null` if the
+ * device did not respond quickly enough.
+ */
+private fun dragHoldAndCapture(
+ activityRule: ActivityTestRule<*>,
+ view: View,
+ viewX: Int,
+ viewY: Int,
+ deltaX: Int,
+ deltaY: Int
+): Bitmap? {
+ val locationOnScreen = IntArray(2)
+ activityRule.runOnUiThread {
+ view.getLocationOnScreen(locationOnScreen)
+ }
+
+ val screenX = locationOnScreen[0]
+ val screenY = locationOnScreen[1]
+
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ // Try 5 times at most. If it fails, just return the null bitmap
+ repeat(5) {
+ var lastMotion = 0L
+ var bitmap: Bitmap? = null
+ dragAndExecute(
+ activityRule = activityRule,
+ screenX = screenX + viewX,
+ screenY = screenY + viewY,
+ deltaX = deltaX,
+ deltaY = deltaY,
+ onFinalMove = {
+ lastMotion = AnimationUtils.currentAnimationTimeMillis()
+ },
+ onUp = {
+ // Now press
+ CtsTouchUtils.injectDownEvent(instrumentation.getUiAutomation(),
+ SystemClock.uptimeMillis(), screenX,
+ screenY, null)
+
+ val downInjected = AnimationUtils.currentAnimationTimeMillis()
+
+ // The receding time is 600 ms, but we don't want to be near the final
+ // part of the animation when the pixels may overlap.
+ if (downInjected - lastMotion < 400) {
+ // Now make sure that we wait until the release should normally have finished:
+ sleepAnimationTime(600)
+
+ bitmap = takeScreenshot(
+ activityRule.activity.window,
+ screenX,
+ screenY,
+ view.width,
+ view.height
+ )
+ }
+ }
+ )
+
+ CtsTouchUtils.injectUpEvent(instrumentation.getUiAutomation(),
+ SystemClock.uptimeMillis(), false,
+ screenX, screenY, null)
+
+ if (bitmap != null) {
+ return bitmap // success!
+ }
+ }
+ return null // timing didn't allow for success this time, so return a null
+}
+
+/**
+ * Drags down on [view] and ensures that the blue rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragDownStretches(
+ activityRule: ActivityTestRule<*>,
+ view: View
+): Boolean {
+ val bitmap = dragAndCapture(
+ activityRule,
+ view,
+ 0,
+ 0,
+ 0,
+ 50
+ )
+
+ // The blue should stretch beyond its normal dimensions
+ return bitmap.getPixel(45, 52) == Color.BLUE
+}
+
+/**
+ * Drags right on [view] and ensures that the blue rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragRightStretches(
+ activityRule: ActivityTestRule<*>,
+ view: View
+): Boolean {
+ val bitmap = dragAndCapture(
+ activityRule,
+ view,
+ 0,
+ 0,
+ 50,
+ 0
+ )
+
+ // The blue should stretch beyond its normal dimensions
+ return bitmap.getPixel(52, 45) == Color.BLUE
+}
+
+/**
+ * Drags up on [view] and ensures that the magenta rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragUpStretches(
+ activityRule: ActivityTestRule<*>,
+ view: View
+): Boolean {
+ val bitmap = dragAndCapture(
+ activityRule,
+ view,
+ 0,
+ 89,
+ 0,
+ -50
+ )
+
+ // The magenta should stretch beyond its normal dimensions
+ return bitmap.getPixel(45, 38) == Color.MAGENTA
+}
+
+/**
+ * Drags left on [view] and ensures that the magenta rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragLeftStretches(
+ activityRule: ActivityTestRule<*>,
+ view: View
+): Boolean {
+ val bitmap = dragAndCapture(
+ activityRule,
+ view,
+ 89,
+ 0,
+ -50,
+ 0
+ )
+
+ // The magenta should stretch beyond its normal dimensions
+ return bitmap.getPixel(38, 45) == Color.MAGENTA
+}
+
+/**
+ * Drags down, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragDownTapAndHoldStretches(
+ activityRule: ActivityTestRule<*>,
+ view: View
+): Boolean {
+ val bitmap = dragHoldAndCapture(
+ activityRule,
+ view,
+ 0,
+ 0,
+ 0,
+ 89
+ ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
+
+ // The blue should stretch beyond its normal dimensions
+ return bitmap.getPixel(45, 52) == Color.BLUE
+}
+
+/**
+ * Drags right, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragRightTapAndHoldStretches(
+ activityRule: ActivityTestRule<*>,
+ view: View
+): Boolean {
+ val bitmap = dragHoldAndCapture(
+ activityRule,
+ view,
+ 0,
+ 0,
+ 89,
+ 0
+ ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
+
+ // The blue should stretch beyond its normal dimensions
+ return bitmap.getPixel(52, 45) == Color.BLUE
+}
+
+/**
+ * Drags up, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragUpTapAndHoldStretches(
+ activityRule: ActivityTestRule<*>,
+ view: View
+): Boolean {
+ val bitmap = dragHoldAndCapture(
+ activityRule,
+ view,
+ 0,
+ 89,
+ 0,
+ -89
+ ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
+
+ // The magenta should stretch beyond its normal dimensions
+ return bitmap.getPixel(45, 38) == Color.MAGENTA
+}
+
+/**
+ * Drags left, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragLeftTapAndHoldStretches(
+ activityRule: ActivityTestRule<*>,
+ view: View
+): Boolean {
+ val bitmap = dragHoldAndCapture(
+ activityRule,
+ view,
+ 89,
+ 0,
+ -89,
+ 0
+ ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
+
+ // The magenta should stretch beyond its normal dimensions
+ return bitmap.getPixel(38, 45) == Color.MAGENTA
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
new file mode 100644
index 0000000..b99028a
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
+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.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkSuggestion;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+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.List;
+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#isMultiStaConcurrencySupported()}.
+ *
+ * Tests the entire connection flow using {@link WifiNetworkSuggestion} which has
+ * {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)} or
+ * {@link WifiNetworkSuggestion.Builder#setOemPrivate(boolean)} set along with a concurrent internet
+ * connection using {@link WifiManager#connect(int, WifiManager.ActionListener)}.
+ *
+ * Note: This feature is only applicable on automotive platforms. So, the test is skipped for
+ * non-automotive platforms.
+ *
+ * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
+ *
+ * TODO(b/177591382): Refactor some of the utilities to a separate file that are copied over from
+ * WifiManagerTest & WifiNetworkSpecifierTest.
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest extends WifiJUnit4TestBase {
+ private static final String TAG = "MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest";
+ private static boolean sWasVerboseLoggingEnabled;
+ private static boolean sWasScanThrottleEnabled;
+ private static boolean sWasWifiEnabled;
+
+ private Context mContext;
+ private WifiManager mWifiManager;
+ private ConnectivityManager mConnectivityManager;
+ private UiDevice mUiDevice;
+ private WifiConfiguration mTestNetworkForRestrictedConnection;
+ private WifiConfiguration mTestNetworkForInternetConnection;
+ private ConnectivityManager.NetworkCallback mNetworkCallback;
+ private ConnectivityManager.NetworkCallback mNsNetworkCallback;
+ private ScheduledExecutorService mExecutorService;
+ private TestHelper mTestHelper;
+
+ private static final int DURATION_MILLIS = 10_000;
+
+ private static boolean hasAutomotiveFeature(Context ctx) {
+ return ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ // skip the test if WiFi is not supported or not automotive platform.
+ // Don't use assumeTrue in @BeforeClass
+ if (!WifiFeature.isWifiSupported(context) || !hasAutomotiveFeature(context)) return;
+
+ 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
+ sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.isWifiEnabled());
+ if (!wifiManager.isWifiEnabled()) {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+ }
+ PollingCheck.check("Wifi not enabled", DURATION_MILLIS, () -> wifiManager.isWifiEnabled());
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ if (!WifiFeature.isWifiSupported(context) || !hasAutomotiveFeature(context)) return;
+
+ 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 {
+ 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 or not automitve platform.
+ assumeTrue(WifiFeature.isWifiSupported(mContext));
+ assumeTrue(hasAutomotiveFeature(mContext));
+ // skip the test if location is not supported
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION));
+ // skip if multi STA not supported.
+ assumeTrue(mWifiManager.isMultiStaConcurrencySupported());
+
+ assertWithMessage("Please enable location for this test!").that(
+ mContext.getSystemService(LocationManager.class).isLocationEnabled()).isTrue();
+
+ // turn screen on
+ mTestHelper.turnScreenOn();
+
+ // Clear any existing app state before each test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+
+ // We need 2 AP's for the test. If there are 2 networks saved on the device and in range,
+ // use those. Otherwise, check if there are 2 BSSID's in range for the only saved network.
+ // This assumes a CTS test environment with at least 2 connectable bssid's (Is that ok?).
+ List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.getPrivilegedConfiguredNetworks());
+ List<WifiConfiguration> matchingNetworksWithBssid =
+ TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks);
+ assertWithMessage("Need at least 2 saved network bssids in range").that(
+ matchingNetworksWithBssid.size()).isAtLeast(2);
+ // Pick any 2 bssid for test.
+ mTestNetworkForRestrictedConnection = matchingNetworksWithBssid.get(0);
+ // Try to find a bssid for another saved network in range. If none exists, fallback
+ // to using 2 bssid's for the same network.
+ mTestNetworkForInternetConnection = matchingNetworksWithBssid.stream()
+ .filter(w -> !w.SSID.equals(mTestNetworkForRestrictedConnection.SSID))
+ .findAny()
+ .orElse(matchingNetworksWithBssid.get(1));
+
+ // Disconnect & disable auto-join on the saved network to prevent auto-connect from
+ // interfering with the test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ for (WifiConfiguration savedNetwork : savedNetworks) {
+ mWifiManager.disableNetwork(savedNetwork.networkId);
+ }
+ mWifiManager.disconnect();
+ });
+
+ // Wait for Wifi to be disconnected.
+ PollingCheck.check(
+ "Wifi not disconnected",
+ 20_000,
+ () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Re-enable networks.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
+ mWifiManager.enableNetwork(savedNetwork.networkId, false);
+ }
+ });
+ // Release the requests after the test.
+ if (mNetworkCallback != null) {
+ mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+ }
+ if (mNsNetworkCallback != null) {
+ mConnectivityManager.unregisterNetworkCallback(mNsNetworkCallback);
+ }
+ mExecutorService.shutdownNow();
+ // Clear any existing app state after each test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+ mTestHelper.turnScreenOff();
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Connect to a network using restricted suggestion API.
+ * 3. Verify that both connections are active.
+ */
+ @Test
+ public void testConnectToOemPaidSuggestionWhenConnectedToInternetNetwork() throws Exception {
+ // First trigger internet connectivity.
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
+
+ // Now trigger restricted connection.
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForRestrictedConnection)
+ .setOemPaid(true)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+ mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+ NET_CAPABILITY_OEM_PAID);
+
+ // Ensure that there are 2 wifi connections available for apps.
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using restricted suggestion API.
+ * 2. Connect to a network using internet connectivity API.
+ * 3. Verify that both connections are active.
+ */
+ @Test
+ public void testConnectToInternetNetworkWhenConnectedToOemPaidSuggestion() throws Exception {
+ // First trigger restricted connection.
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForRestrictedConnection)
+ .setOemPaid(true)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+ mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+ NET_CAPABILITY_OEM_PAID);
+
+ // Now trigger internet connectivity.
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
+
+ // Ensure that there are 2 wifi connections available for apps.
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Connect to a network using restricted suggestion API.
+ * 3. Verify that both connections are active.
+ */
+ @Test
+ public void testConnectToOemPrivateSuggestionWhenConnectedToInternetNetwork() throws Exception {
+ // First trigger internet connectivity.
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
+
+ // Now trigger restricted connection.
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForRestrictedConnection)
+ .setOemPrivate(true)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+ mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+ NET_CAPABILITY_OEM_PRIVATE);
+
+ // Ensure that there are 2 wifi connections available for apps.
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using restricted suggestion API.
+ * 2. Connect to a network using internet connectivity API.
+ * 3. Verify that both connections are active.
+ */
+ @Test
+ public void testConnectToInternetNetworkWhenConnectedToOemPrivateSuggestion() throws Exception {
+ // First trigger restricted connection.
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForRestrictedConnection)
+ .setOemPrivate(true)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+ mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+ NET_CAPABILITY_OEM_PRIVATE);
+
+ // Now trigger internet connectivity.
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
+
+ // Ensure that there are 2 wifi connections available for apps.
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Simulate connection failure to a network using restricted suggestion API & different net
+ * capability (need corresponding net capability requested for platform to connect).
+ * 3. Verify that only 1 connection is active.
+ */
+ @Test
+ public void testConnectToOemPaidSuggestionFailureWhenConnectedToInternetNetwork()
+ throws Exception {
+ // First trigger internet connectivity.
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
+
+ // Now trigger restricted connection.
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForRestrictedConnection)
+ .setOemPaid(true)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+ mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+ NET_CAPABILITY_OEM_PRIVATE);
+
+ // Ensure that there is only 1 connection available for apps.
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(1);
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Simulate connection failure to a network using restricted suggestion API & different net
+ * capability (need corresponding net capability requested for platform to connect).
+ * 3. Verify that only 1 connection is active.
+ */
+ @Test
+ public void testConnectToOemPrivateSuggestionFailureWhenConnectedToInternetNetwork()
+ throws Exception {
+ // First trigger internet connectivity.
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
+
+ // Now trigger restricted connection.
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForRestrictedConnection)
+ .setOemPrivate(true)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+ mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+ NET_CAPABILITY_OEM_PAID);
+
+ // Ensure that there is only 1 connection available for apps.
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(1);
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Simulate connection failure to a restricted network using suggestion API & restricted net
+ * capability (need corresponding restricted bit set in suggestion for platform to connect).
+ * 3. Verify that only 1 connection is active.
+ */
+ @Test
+ public void
+ testConnectToSuggestionFailureWithOemPaidNetCapabilityWhenConnectedToInternetNetwork()
+ throws Exception {
+ // First trigger internet connectivity.
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
+
+ // Now trigger restricted connection.
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForRestrictedConnection)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+ mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+ NET_CAPABILITY_OEM_PAID);
+
+ // Ensure that there is only 1 connection available for apps.
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(1);
+ }
+
+ /**
+ * Tests the concurrent connection flow.
+ * 1. Connect to a network using internet connectivity API.
+ * 2. Simulate connection failure to a restricted network using suggestion API & restricted net
+ * capability (need corresponding restricted bit set in suggestion for platform to connect).
+ * 3. Verify that only 1 connection is active.
+ */
+ @Test
+ public void
+ testConnectToSuggestionFailureWithOemPrivateNetCapabilityWhenConnectedToInternetNetwork()
+ throws Exception {
+ // First trigger internet connectivity.
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
+
+ // Now trigger restricted connection.
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetworkForRestrictedConnection)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+ mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
+ NET_CAPABILITY_OEM_PRIVATE);
+
+ // Ensure that there is only 1 connection available for apps.
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(1);
+ }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
index 0c12dcd..c65c214 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
@@ -16,39 +16,23 @@
package android.net.wifi.cts;
-import static android.net.NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
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.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
-import android.annotation.NonNull;
-import android.app.UiAutomation;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.MacAddress;
-import android.net.Network;
-import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.NetworkRequestMatchCallback;
import android.net.wifi.WifiNetworkSpecifier;
-import android.os.WorkSource;
import android.platform.test.annotations.AppModeFull;
import android.support.test.uiautomator.UiDevice;
-import android.text.TextUtils;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
@@ -57,8 +41,6 @@
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.SystemUtil;
-
import org.junit.After;
import org.junit.AfterClass;
@@ -67,13 +49,7 @@
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.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
/**
* Tests multiple concurrent connection flow on devices that support multi STA concurrency
@@ -107,8 +83,9 @@
private UiDevice mUiDevice;
private WifiConfiguration mTestNetworkForPeerToPeer;
private WifiConfiguration mTestNetworkForInternetConnection;
- private TestNetworkCallback mNetworkCallback;
- private TestNetworkCallback mNrNetworkCallback;
+ private ConnectivityManager.NetworkCallback mNetworkCallback;
+ private ConnectivityManager.NetworkCallback mNrNetworkCallback;
+ private TestHelper mTestHelper;
private static final int DURATION = 10_000;
private static final int DURATION_UI_INTERACTION = 25_000;
@@ -123,7 +100,7 @@
if (!WifiFeature.isWifiSupported(context)) return;
WifiManager wifiManager = context.getSystemService(WifiManager.class);
- assertNotNull(wifiManager);
+ assertThat(wifiManager).isNotNull();
// turn on verbose logging for tests
sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
@@ -139,7 +116,9 @@
// enable Wifi
sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
() -> wifiManager.isWifiEnabled());
- if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+ if (!wifiManager.isWifiEnabled()) {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+ }
PollingCheck.check("Wifi not enabled", DURATION, () -> wifiManager.isWifiEnabled());
}
@@ -149,7 +128,7 @@
if (!WifiFeature.isWifiSupported(context)) return;
WifiManager wifiManager = context.getSystemService(WifiManager.class);
- assertNotNull(wifiManager);
+ assertThat(wifiManager).isNotNull();
ShellIdentityUtils.invokeWithShellPermissions(
() -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
@@ -165,6 +144,7 @@
mWifiManager = mContext.getSystemService(WifiManager.class);
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mTestHelper = new TestHelper(mContext, mUiDevice);
// skip the test if WiFi is not supported
assumeTrue(WifiFeature.isWifiSupported(mContext));
@@ -173,11 +153,12 @@
// skip if multi STA not supported.
assumeTrue(mWifiManager.isMultiStaConcurrencySupported());
- assertTrue("Please enable location for this test!",
- mContext.getSystemService(LocationManager.class).isLocationEnabled());
+ assertWithMessage("Please enable location for this test!")
+ .that(mContext.getSystemService(LocationManager.class).isLocationEnabled())
+ .isTrue();
// turn screen on
- turnScreenOn();
+ mTestHelper.turnScreenOn();
// Clear any existing app state before each test.
ShellIdentityUtils.invokeWithShellPermissions(
@@ -189,12 +170,17 @@
List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.getPrivilegedConfiguredNetworks());
List<WifiConfiguration> matchingNetworksWithBssid =
- findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks);
- assertTrue("Need at least 2 saved network bssids in range",
- matchingNetworksWithBssid.size() >= 2);
+ TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks);
+ assertWithMessage("Need at least 2 saved network bssids in range")
+ .that(matchingNetworksWithBssid.size()).isAtLeast(2);
// Pick any 2 bssid for test.
mTestNetworkForPeerToPeer = matchingNetworksWithBssid.get(0);
- mTestNetworkForInternetConnection = matchingNetworksWithBssid.get(1);
+ // Try to find a bssid for another saved network in range. If none exists, fallback
+ // to using 2 bssid's for the same network.
+ mTestNetworkForInternetConnection = matchingNetworksWithBssid.stream()
+ .filter(w -> !w.SSID.equals(mTestNetworkForPeerToPeer.SSID))
+ .findAny()
+ .orElse(matchingNetworksWithBssid.get(1));
// Disconnect & disable auto-join on the saved network to prevent auto-connect from
// interfering with the test.
@@ -232,394 +218,19 @@
// Clear any existing app state after each test.
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
- turnScreenOff();
- }
-
- private static void setWifiEnabled(boolean enable) throws Exception {
- // now trigger the change using shell commands.
- SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
- }
-
- private void turnScreenOn() throws Exception {
- mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
- mUiDevice.executeShellCommand("wm dismiss-keyguard");
- // Since the screen on/off intent is ordered, they will not be sent right now.
- Thread.sleep(DURATION_SCREEN_TOGGLE);
- }
-
- private void turnScreenOff() throws Exception {
- mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
- // Since the screen on/off intent is ordered, they will not be sent right now.
- Thread.sleep(DURATION_SCREEN_TOGGLE);
- }
-
- private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
- private final CountDownLatch mCountDownLatch;
- public boolean onAvailableCalled = false;
-
- TestScanResultsCallback(CountDownLatch countDownLatch) {
- mCountDownLatch = countDownLatch;
- }
-
- @Override
- public void onScanResultsAvailable() {
- onAvailableCalled = true;
- mCountDownLatch.countDown();
- }
- }
-
- /**
- * Loops through all the saved networks available in the scan results. Returns a list 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.
- */
- private 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);
- 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);
- try {
- wifiManager.registerScanResultsCallback(
- Executors.newSingleThreadExecutor(), scanResultsCallback);
- wifiManager.startScan(new WorkSource(myUid()));
- // now wait for callback
- assertTrue(countDownLatch.await(
- DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- } finally {
- wifiManager.unregisterScanResultsCallback(scanResultsCallback);
- }
- List<ScanResult> scanResults = wifiManager.getScanResults();
- if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
- for (ScanResult scanResult : scanResults) {
- WifiConfiguration matchingNetwork = savedNetworks.stream()
- .filter(network -> TextUtils.equals(
- scanResult.SSID, removeDoubleQuotes(network.SSID)))
- .findAny()
- .orElse(null);
- if (matchingNetwork != null) {
- // 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);
- }
- }
- if (!matchingNetworksWithBssids.isEmpty()) break;
- }
- return matchingNetworksWithBssids;
- }
-
- private void assertConnectionEquals(@NonNull WifiConfiguration network,
- @NonNull WifiInfo wifiInfo) {
- assertEquals(network.SSID, wifiInfo.getSSID());
- assertEquals(network.BSSID, wifiInfo.getBSSID());
- }
-
- private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
- private final CountDownLatch mCountDownLatch;
- public boolean onAvailableCalled = false;
- public boolean onUnavailableCalled = false;
- public NetworkCapabilities networkCapabilities;
-
- TestNetworkCallback(CountDownLatch countDownLatch) {
- mCountDownLatch = countDownLatch;
- }
-
- @Override
- public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
- LinkProperties linkProperties, boolean blocked) {
- onAvailableCalled = true;
- this.networkCapabilities = networkCapabilities;
- mCountDownLatch.countDown();
- }
-
- @Override
- public void onUnavailable() {
- onUnavailableCalled = true;
- mCountDownLatch.countDown();
- }
- }
-
- private static class TestNetworkRequestMatchCallback implements NetworkRequestMatchCallback {
- private final Object mLock;
-
- public boolean onRegistrationCalled = false;
- public boolean onAbortCalled = false;
- public boolean onMatchCalled = false;
- public boolean onConnectSuccessCalled = false;
- public boolean onConnectFailureCalled = false;
- public WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback = null;
- public List<ScanResult> matchedScanResults = null;
-
- TestNetworkRequestMatchCallback(Object lock) {
- mLock = lock;
- }
-
- @Override
- public void onUserSelectionCallbackRegistration(
- WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback) {
- synchronized (mLock) {
- onRegistrationCalled = true;
- this.userSelectionCallback = userSelectionCallback;
- mLock.notify();
- }
- }
-
- @Override
- public void onAbort() {
- synchronized (mLock) {
- onAbortCalled = true;
- mLock.notify();
- }
- }
-
- @Override
- public void onMatch(List<ScanResult> scanResults) {
- synchronized (mLock) {
- // This can be invoked multiple times. So, ignore after the first one to avoid
- // disturbing the rest of the test sequence.
- if (onMatchCalled) return;
- onMatchCalled = true;
- matchedScanResults = scanResults;
- mLock.notify();
- }
- }
-
- @Override
- public void onUserSelectionConnectSuccess(WifiConfiguration config) {
- synchronized (mLock) {
- onConnectSuccessCalled = true;
- mLock.notify();
- }
- }
-
- @Override
- public void onUserSelectionConnectFailure(WifiConfiguration config) {
- synchronized (mLock) {
- onConnectFailureCalled = true;
- mLock.notify();
- }
- }
- }
-
- private void handleUiInteractions(WifiConfiguration network, boolean shouldUserReject) {
- // can't use CountDownLatch since there are many callbacks expected and CountDownLatch
- // cannot be reset.
- // TODO(b/177591382): Use ArrayBlockingQueue/LinkedBlockingQueue
- Object uiLock = new Object();
- UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- TestNetworkRequestMatchCallback networkRequestMatchCallback =
- new TestNetworkRequestMatchCallback(uiLock);
- try {
- uiAutomation.adoptShellPermissionIdentity();
-
- // 1. Wait for registration callback.
- synchronized (uiLock) {
- try {
- mWifiManager.registerNetworkRequestMatchCallback(
- Executors.newSingleThreadExecutor(), networkRequestMatchCallback);
- uiLock.wait(DURATION_UI_INTERACTION);
- } catch (InterruptedException e) {
- }
- }
- assertTrue(networkRequestMatchCallback.onRegistrationCalled);
- assertNotNull(networkRequestMatchCallback.userSelectionCallback);
-
- // 2. Wait for matching scan results
- synchronized (uiLock) {
- try {
- uiLock.wait(DURATION_UI_INTERACTION);
- } catch (InterruptedException e) {
- }
- }
- assertTrue(networkRequestMatchCallback.onMatchCalled);
- assertNotNull(networkRequestMatchCallback.matchedScanResults);
- assertThat(networkRequestMatchCallback.matchedScanResults.size()).isAtLeast(1);
-
- // 3. Trigger connection to one of the matched networks or reject the request.
- if (shouldUserReject) {
- networkRequestMatchCallback.userSelectionCallback.reject();
- } else {
- networkRequestMatchCallback.userSelectionCallback.select(network);
- }
-
- // 4. Wait for connection success or abort.
- synchronized (uiLock) {
- try {
- uiLock.wait(DURATION_UI_INTERACTION);
- } catch (InterruptedException e) {
- }
- }
- if (shouldUserReject) {
- assertTrue(networkRequestMatchCallback.onAbortCalled);
- } else {
- assertTrue(networkRequestMatchCallback.onConnectSuccessCalled);
- }
- } finally {
- mWifiManager.unregisterNetworkRequestMatchCallback(networkRequestMatchCallback);
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
-
- /**
- * Tests the entire connection flow using the provided specifier.
- *
- * @param specifier Specifier to use for network request.
- * @param shouldUserReject Whether to simulate user rejection or not.
- */
- private void testConnectionFlowWithSpecifier(
- WifiConfiguration network, WifiNetworkSpecifier specifier, boolean shouldUserReject) {
- CountDownLatch countDownLatch = new CountDownLatch(1);
- // Fork a thread to handle the UI interactions.
- Thread uiThread = new Thread(() -> handleUiInteractions(network, shouldUserReject));
-
- // File the network request & wait for the callback.
- mNrNetworkCallback = new TestNetworkCallback(countDownLatch);
- try {
- // File a request for wifi network.
- mConnectivityManager.requestNetwork(
- new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_WIFI)
- .removeCapability(NET_CAPABILITY_INTERNET)
- .setNetworkSpecifier(specifier)
- .build(),
- mNrNetworkCallback);
- // Wait for the request to reach the wifi stack before kick-starting the UI
- // interactions.
- Thread.sleep(100);
- // Start the UI interactions.
- uiThread.run();
- // now wait for callback
- assertTrue(countDownLatch.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- }
- if (shouldUserReject) {
- assertTrue(mNrNetworkCallback.onUnavailableCalled);
- } else {
- assertTrue(mNrNetworkCallback.onAvailableCalled);
- assertConnectionEquals(
- network, (WifiInfo) mNrNetworkCallback.networkCapabilities.getTransportInfo());
- }
-
- try {
- // Ensure that the UI interaction thread has completed.
- uiThread.join(DURATION_UI_INTERACTION);
- } catch (InterruptedException e) {
- fail("UI interaction interrupted");
- }
+ mTestHelper.turnScreenOff();
}
private void testSuccessfulConnectionWithSpecifier(
WifiConfiguration network, WifiNetworkSpecifier specifier) {
- testConnectionFlowWithSpecifier(network, specifier, false);
+ mNrNetworkCallback = mTestHelper.testConnectionFlowWithSpecifier(
+ network, specifier, false);
}
private void testUserRejectionWithSpecifier(
WifiConfiguration network, WifiNetworkSpecifier specifier) {
- testConnectionFlowWithSpecifier(network, specifier, true);
- }
-
- private static String removeDoubleQuotes(String string) {
- return WifiInfo.sanitizeSsid(string);
- }
-
- private static class TestActionListener implements WifiManager.ActionListener {
- private final CountDownLatch mCountDownLatch;
- public boolean onSuccessCalled = false;
- public boolean onFailedCalled = false;
- public int failureReason = -1;
-
- TestActionListener(CountDownLatch countDownLatch) {
- mCountDownLatch = countDownLatch;
- }
-
- @Override
- public void onSuccess() {
- onSuccessCalled = true;
- mCountDownLatch.countDown();
- }
-
- @Override
- public void onFailure(int reason) {
- onFailedCalled = true;
- mCountDownLatch.countDown();
- }
- }
-
- /**
- * Triggers connection to one of the saved networks using {@link WifiManager#connect(
- * WifiConfiguration, WifiManager.ActionListener)}
- */
- private void testConnectionFlowWithConnect(@NonNull WifiConfiguration network) {
- CountDownLatch countDownLatchAl = new CountDownLatch(1);
- CountDownLatch countDownLatchNr = new CountDownLatch(1);
- TestActionListener actionListener = new TestActionListener(countDownLatchAl);
- mNetworkCallback = new TestNetworkCallback(countDownLatchNr);
- UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- // File a callback for wifi network.
- mConnectivityManager.registerNetworkCallback(
- new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_WIFI)
- .addCapability(NET_CAPABILITY_INTERNET)
- .build(),
- mNetworkCallback);
- // Trigger the connection.
- mWifiManager.connect(network, actionListener);
- // now wait for action listener callback
- assertTrue(countDownLatchAl.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
- // check if we got the success callback
- assertTrue(actionListener.onSuccessCalled);
-
- // Wait for connection to complete & ensure we are connected to the saved network.
- assertTrue(countDownLatchNr.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
- assertTrue(mNetworkCallback.onAvailableCalled);
- assertConnectionEquals(
- network, (WifiInfo) mNetworkCallback.networkCapabilities.getTransportInfo());
- } catch (InterruptedException e) {
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
- private static WifiNetworkSpecifier.Builder
- createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
- @NonNull WifiConfiguration network) {
- WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder()
- .setSsid(removeDoubleQuotes(network.SSID))
- .setBssid(MacAddress.fromString(network.BSSID));
- if (network.preSharedKey != null) {
- if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
- specifierBuilder.setWpa2Passphrase(removeDoubleQuotes(network.preSharedKey));
- } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
- specifierBuilder.setWpa3Passphrase(removeDoubleQuotes(network.preSharedKey));
- } else {
- fail("Unsupported security type found in saved networks");
- }
- } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
- specifierBuilder.setIsEnhancedOpen(true);
- } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
- fail("Unsupported security type found in saved networks");
- }
- specifierBuilder.setIsHiddenSsid(network.hiddenSSID);
- return specifierBuilder;
- }
-
- private long getNumWifiConnections() {
- Network[] networks = mConnectivityManager.getAllNetworks();
- return Arrays.stream(networks)
- .filter(n ->
- mConnectivityManager.getNetworkCapabilities(n).hasTransport(TRANSPORT_WIFI))
- .count();
+ mNrNetworkCallback = mTestHelper.testConnectionFlowWithSpecifier(
+ network, specifier, true);
}
/**
@@ -631,17 +242,18 @@
@Test
public void testConnectToPeerPeerNetworkWhenConnectedToInternetNetwork() throws Exception {
// First trigger internet connectivity.
- testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
// Now trigger peer to peer connectivity.
WifiNetworkSpecifier specifier =
- createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+ TestHelper.createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
mTestNetworkForPeerToPeer)
.build();
testSuccessfulConnectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
// Ensure that there are 2 wifi connections available for apps.
- assertEquals(2, getNumWifiConnections());
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
}
/**
@@ -654,16 +266,17 @@
public void testConnectToInternetNetworkWhenConnectedToPeerPeerNetwork() throws Exception {
// First trigger peer to peer connectivity.
WifiNetworkSpecifier specifier =
- createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+ TestHelper.createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
mTestNetworkForPeerToPeer)
.build();
testSuccessfulConnectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
// Now trigger internet connectivity.
- testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
// Ensure that there are 2 wifi connections available for apps.
- assertEquals(2, getNumWifiConnections());
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
}
/**
@@ -675,16 +288,17 @@
@Test
public void testPeerToPeerConnectionRejectWhenConnectedToInternetNetwork() throws Exception {
// First trigger internet connectivity.
- testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+ mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
+ mTestNetworkForInternetConnection);
// Now trigger peer to peer connectivity.
WifiNetworkSpecifier specifier =
- createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+ TestHelper.createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
mTestNetworkForPeerToPeer)
.build();
testUserRejectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
// Ensure that there is only 1 wifi connection available for apps.
- assertEquals(1, getNumWifiConnections());
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(1);
}
}
\ No newline at end of file
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
new file mode 100644
index 0000000..0cb6f23
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+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;
+import static android.os.Process.myUid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkCapabilitiesProto;
+import android.net.NetworkRequest;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkSpecifier;
+import android.net.wifi.WifiNetworkSuggestion;
+import android.os.WorkSource;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class to hold helper methods that are repeated across wifi CTS tests.
+ */
+public class TestHelper {
+ private static final String TAG = "WifiTestHelper";
+
+ private final Context mContext;
+ private final WifiManager mWifiManager;
+ private final ConnectivityManager mConnectivityManager;
+ private final UiDevice mUiDevice;
+
+ private static final int DURATION_MILLIS = 10_000;
+ private static final int DURATION_NETWORK_CONNECTION_MILLIS = 60_000;
+ private static final int DURATION_SCREEN_TOGGLE_MILLIS = 2000;
+ private static final int DURATION_UI_INTERACTION_MILLIS = 25_000;
+ private static final int SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID = 3;
+
+ public TestHelper(@NonNull Context context, @NonNull UiDevice uiDevice) {
+ mContext = context;
+ mWifiManager = context.getSystemService(WifiManager.class);
+ mConnectivityManager = context.getSystemService(ConnectivityManager.class);
+ mUiDevice = uiDevice;
+ }
+
+ public void turnScreenOn() throws Exception {
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ mUiDevice.executeShellCommand("wm dismiss-keyguard");
+ // Since the screen on/off intent is ordered, they will not be sent right now.
+ Thread.sleep(DURATION_SCREEN_TOGGLE_MILLIS);
+ }
+
+ public void turnScreenOff() throws Exception {
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ // Since the screen on/off intent is ordered, they will not be sent right now.
+ Thread.sleep(DURATION_SCREEN_TOGGLE_MILLIS);
+ }
+
+ private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
+ private final CountDownLatch mCountDownLatch;
+ public boolean onAvailableCalled = false;
+
+ TestScanResultsCallback(CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ }
+
+ @Override
+ public void onScanResultsAvailable() {
+ onAvailableCalled = true;
+ mCountDownLatch.countDown();
+ }
+ }
+
+ /**
+ * Loops through all the saved networks available in the scan results. Returns a list 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.
+ */
+ 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);
+ 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);
+ try {
+ wifiManager.registerScanResultsCallback(
+ Executors.newSingleThreadExecutor(), scanResultsCallback);
+ wifiManager.startScan(new WorkSource(myUid()));
+ // now wait for callback
+ assertThat(countDownLatch.await(
+ DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ } finally {
+ wifiManager.unregisterScanResultsCallback(scanResultsCallback);
+ }
+ List<ScanResult> scanResults = wifiManager.getScanResults();
+ if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+ for (ScanResult scanResult : scanResults) {
+ WifiConfiguration matchingNetwork = savedNetworks.stream()
+ .filter(network -> TextUtils.equals(
+ scanResult.SSID, WifiInfo.sanitizeSsid(network.SSID)))
+ .findAny()
+ .orElse(null);
+ if (matchingNetwork != null) {
+ // 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);
+ }
+ }
+ if (!matchingNetworksWithBssids.isEmpty()) break;
+ }
+ return matchingNetworksWithBssids;
+ }
+
+ /**
+ * Convert the provided saved network to a corresponding suggestion builder.
+ */
+ public static WifiNetworkSuggestion.Builder
+ createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ @NonNull WifiConfiguration network) {
+ WifiNetworkSuggestion.Builder suggestionBuilder = new WifiNetworkSuggestion.Builder()
+ .setSsid(WifiInfo.sanitizeSsid(network.SSID))
+ .setBssid(MacAddress.fromString(network.BSSID));
+ if (network.preSharedKey != null) {
+ if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+ suggestionBuilder.setWpa2Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+ } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+ suggestionBuilder.setWpa3Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+ } else {
+ fail("Unsupported security type found in saved networks");
+ }
+ } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+ suggestionBuilder.setIsEnhancedOpen(true);
+ } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+ fail("Unsupported security type found in saved networks");
+ }
+ suggestionBuilder.setIsHiddenSsid(network.hiddenSSID);
+ return suggestionBuilder;
+ }
+
+
+ /**
+ * Convert the provided saved network to a corresponding specifier builder.
+ */
+ public static WifiNetworkSpecifier.Builder createSpecifierBuilderWithCredentialFromSavedNetwork(
+ @NonNull WifiConfiguration network) {
+ WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder()
+ .setSsid(WifiInfo.sanitizeSsid(network.SSID));
+ if (network.preSharedKey != null) {
+ if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+ specifierBuilder.setWpa2Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+ } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+ specifierBuilder.setWpa3Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+ } else {
+ fail("Unsupported security type found in saved networks");
+ }
+ } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+ specifierBuilder.setIsEnhancedOpen(true);
+ } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+ fail("Unsupported security type found in saved networks");
+ }
+ specifierBuilder.setIsHiddenSsid(network.hiddenSSID);
+ return specifierBuilder;
+ }
+
+ /**
+ * Convert the provided saved network to a corresponding specifier builder.
+ */
+ public static WifiNetworkSpecifier.Builder
+ createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+ @NonNull WifiConfiguration network) {
+ return createSpecifierBuilderWithCredentialFromSavedNetwork(network)
+ .setBssid(MacAddress.fromString(network.BSSID));
+ }
+
+ private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final CountDownLatch mCountDownLatch;
+ public boolean onAvailableCalled = false;
+ public boolean onUnavailableCalled = false;
+ public NetworkCapabilities networkCapabilities;
+
+ TestNetworkCallback(CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ }
+
+ @Override
+ public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
+ LinkProperties linkProperties, boolean blocked) {
+ onAvailableCalled = true;
+ this.networkCapabilities = networkCapabilities;
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onUnavailable() {
+ onUnavailableCalled = true;
+ mCountDownLatch.countDown();
+ }
+ }
+
+ private static void assertConnectionEquals(@NonNull WifiConfiguration network,
+ @NonNull WifiInfo wifiInfo) {
+ assertThat(network.SSID).isEqualTo(wifiInfo.getSSID());
+ assertThat(network.BSSID).isEqualTo(wifiInfo.getBSSID());
+ }
+
+ private static class TestActionListener implements WifiManager.ActionListener {
+ private final CountDownLatch mCountDownLatch;
+ public boolean onSuccessCalled = false;
+ public boolean onFailedCalled = false;
+ public int failureReason = -1;
+
+ TestActionListener(CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ }
+
+ @Override
+ public void onSuccess() {
+ onSuccessCalled = true;
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ onFailedCalled = true;
+ mCountDownLatch.countDown();
+ }
+ }
+
+ /**
+ * Triggers connection to one of the saved networks using {@link WifiManager#connect(
+ * WifiConfiguration, WifiManager.ActionListener)}
+ *
+ * @param network saved network from the device to use for the connection.
+ *
+ * @return NetworkCallback used for the connection (can be used by client to release the
+ * connection.
+ */
+ public ConnectivityManager.NetworkCallback testConnectionFlowWithConnect(
+ @NonNull WifiConfiguration network) {
+ CountDownLatch countDownLatchAl = new CountDownLatch(1);
+ CountDownLatch countDownLatchNr = new CountDownLatch(1);
+ TestActionListener actionListener = new TestActionListener(countDownLatchAl);
+ TestNetworkCallback testNetworkCallback = new TestNetworkCallback(countDownLatchNr);
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ // File a callback for wifi network.
+ mConnectivityManager.registerNetworkCallback(
+ new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ // Needed to ensure that the restricted concurrent connection does not
+ // match this request.
+ .addUnwantedCapability(NET_CAPABILITY_OEM_PAID)
+ .addUnwantedCapability(NET_CAPABILITY_OEM_PRIVATE)
+ .build(),
+ testNetworkCallback);
+ // Trigger the connection.
+ mWifiManager.connect(network, actionListener);
+ // now wait for action listener callback
+ assertThat(countDownLatchAl.await(
+ DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ // check if we got the success callback
+ 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.onAvailableCalled).isTrue();
+ assertConnectionEquals(
+ network, (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo());
+ } catch (InterruptedException e) {
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ return testNetworkCallback;
+ }
+
+ /**
+ * Tests the entire connection success flow using the provided suggestion.
+ *
+ * @param network saved network from the device to use for the connection.
+ * @param suggestion suggestion to use for the connection.
+ * @param executorService Excutor service to run scan periodically (to trigger connection).
+ * @param restrictedNetworkCapability Whether this connection should be restricted with
+ * the provided capability.
+ *
+ * @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,
+ @Nullable Integer restrictedNetworkCapability) {
+ return testConnectionFlowWithSuggestionInternal(
+ network, suggestion, executorService, restrictedNetworkCapability, true);
+ }
+
+ /**
+ * Tests the connection failure flow using the provided suggestion.
+ *
+ * @param network saved network from the device to use for the connection.
+ * @param suggestion suggestion to use for the connection.
+ * @param executorService Excutor service to run scan periodically (to trigger connection).
+ * @param restrictedNetworkCapability Whether this connection should be restricted with
+ * the provided capability.
+ *
+ * @return NetworkCallback used for the connection (can be used by client to release the
+ * connection.
+ */
+ public ConnectivityManager.NetworkCallback testConnectionFailureFlowWithSuggestion(
+ WifiConfiguration network, WifiNetworkSuggestion suggestion,
+ @NonNull ScheduledExecutorService executorService,
+ @Nullable Integer restrictedNetworkCapability) {
+ return testConnectionFlowWithSuggestionInternal(
+ network, suggestion, executorService, restrictedNetworkCapability, false);
+ }
+
+ /**
+ * Tests the entire connection success/failure flow using the provided suggestion.
+ *
+ * @param network saved network from the device to use for the connection.
+ * @param suggestion suggestion to use for the connection.
+ * @param executorService Excutor service to run scan periodically (to trigger connection).
+ * @param restrictedNetworkCapability Whether this connection should be restricted with
+ * the provided capability.
+ * @param expectConnectionSuccess Whether to expect connection success or not.
+ *
+ * @return NetworkCallback used for the connection (can be used by client to release the
+ * connection.
+ */
+ private ConnectivityManager.NetworkCallback testConnectionFlowWithSuggestionInternal(
+ WifiConfiguration network, WifiNetworkSuggestion suggestion,
+ @NonNull ScheduledExecutorService executorService,
+ @Nullable Integer restrictedNetworkCapability,
+ boolean expectConnectionSuccess) {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ // File the network request & wait for the callback.
+ TestNetworkCallback testNetworkCallback = new TestNetworkCallback(countDownLatch);
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ // File a request for restricted (oem paid) wifi network.
+ NetworkRequest.Builder nrBuilder = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET);
+ if (restrictedNetworkCapability == null) {
+ // If not a restricted connection, a network callback is sufficient.
+ mConnectivityManager.registerNetworkCallback(
+ nrBuilder.build(), testNetworkCallback);
+ } else {
+ nrBuilder.addCapability(restrictedNetworkCapability);
+ mConnectivityManager.requestNetwork(nrBuilder.build(), testNetworkCallback);
+ }
+ // Add wifi network suggestion.
+ assertThat(mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)))
+ .isEqualTo(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS);
+ // Wait for the request to reach the wifi stack before kick-start periodic scans.
+ Thread.sleep(100);
+ // Step: Trigger scans periodically to trigger network selection quicker.
+ executorService.scheduleAtFixedRate(() -> {
+ if (!mWifiManager.startScan()) {
+ Log.w(TAG, "Failed to trigger scan");
+ }
+ }, 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.onAvailableCalled).isTrue();
+ assertConnectionEquals(
+ network,
+ (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo());
+ } else {
+ // now wait for connection to timeout.
+ assertThat(countDownLatch.await(
+ DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isFalse();
+ }
+ } catch (InterruptedException e) {
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ executorService.shutdown();
+ }
+ return testNetworkCallback;
+ }
+
+ private static class TestNetworkRequestMatchCallback implements
+ WifiManager.NetworkRequestMatchCallback {
+ private final Object mLock;
+
+ public boolean onRegistrationCalled = false;
+ public boolean onAbortCalled = false;
+ public boolean onMatchCalled = false;
+ public boolean onConnectSuccessCalled = false;
+ public boolean onConnectFailureCalled = false;
+ public WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback = null;
+ public List<ScanResult> matchedScanResults = null;
+
+ TestNetworkRequestMatchCallback(Object lock) {
+ mLock = lock;
+ }
+
+ @Override
+ public void onUserSelectionCallbackRegistration(
+ WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback) {
+ synchronized (mLock) {
+ onRegistrationCalled = true;
+ this.userSelectionCallback = userSelectionCallback;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onAbort() {
+ synchronized (mLock) {
+ onAbortCalled = true;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onMatch(List<ScanResult> scanResults) {
+ synchronized (mLock) {
+ // This can be invoked multiple times. So, ignore after the first one to avoid
+ // disturbing the rest of the test sequence.
+ if (onMatchCalled) return;
+ onMatchCalled = true;
+ matchedScanResults = scanResults;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onUserSelectionConnectSuccess(WifiConfiguration config) {
+ synchronized (mLock) {
+ onConnectSuccessCalled = true;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onUserSelectionConnectFailure(WifiConfiguration config) {
+ synchronized (mLock) {
+ onConnectFailureCalled = true;
+ mLock.notify();
+ }
+ }
+ }
+
+ private void handleUiInteractions(WifiConfiguration network, boolean shouldUserReject) {
+ // can't use CountDownLatch since there are many callbacks expected and CountDownLatch
+ // cannot be reset.
+ // TODO(b/177591382): Use ArrayBlockingQueue/LinkedBlockingQueue
+ Object uiLock = new Object();
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ TestNetworkRequestMatchCallback networkRequestMatchCallback =
+ new TestNetworkRequestMatchCallback(uiLock);
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+
+ // 1. Wait for registration callback.
+ synchronized (uiLock) {
+ try {
+ mWifiManager.registerNetworkRequestMatchCallback(
+ Executors.newSingleThreadExecutor(), networkRequestMatchCallback);
+ uiLock.wait(DURATION_UI_INTERACTION_MILLIS);
+ } catch (InterruptedException e) {
+ }
+ }
+ assertThat(networkRequestMatchCallback.onRegistrationCalled).isTrue();
+ assertThat(networkRequestMatchCallback.userSelectionCallback).isNotNull();
+
+ // 2. Wait for matching scan results
+ synchronized (uiLock) {
+ try {
+ uiLock.wait(DURATION_UI_INTERACTION_MILLIS);
+ } catch (InterruptedException e) {
+ }
+ }
+ assertThat(networkRequestMatchCallback.onMatchCalled).isTrue();
+ assertThat(networkRequestMatchCallback.matchedScanResults).isNotNull();
+ assertThat(networkRequestMatchCallback.matchedScanResults.size()).isAtLeast(1);
+
+ // 3. Trigger connection to one of the matched networks or reject the request.
+ if (shouldUserReject) {
+ networkRequestMatchCallback.userSelectionCallback.reject();
+ } else {
+ networkRequestMatchCallback.userSelectionCallback.select(network);
+ }
+
+ // 4. Wait for connection success or abort.
+ synchronized (uiLock) {
+ try {
+ uiLock.wait(DURATION_UI_INTERACTION_MILLIS);
+ } catch (InterruptedException e) {
+ }
+ }
+ if (shouldUserReject) {
+ assertThat(networkRequestMatchCallback.onAbortCalled).isTrue();
+ } else {
+ assertThat(networkRequestMatchCallback.onConnectSuccessCalled).isTrue();
+ }
+ } finally {
+ mWifiManager.unregisterNetworkRequestMatchCallback(networkRequestMatchCallback);
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Tests the entire connection flow using the provided specifier.
+ *
+ * @param specifier Specifier to use for network request.
+ * @param shouldUserReject Whether to simulate user rejection or not.
+ *
+ * @return NetworkCallback used for the connection (can be used by client to release the
+ * connection.
+ */
+ public ConnectivityManager.NetworkCallback testConnectionFlowWithSpecifier(
+ WifiConfiguration network, WifiNetworkSpecifier specifier, boolean shouldUserReject) {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ // Fork a thread to handle the UI interactions.
+ Thread uiThread = new Thread(() -> handleUiInteractions(network, shouldUserReject));
+
+ // File the network request & wait for the callback.
+ TestNetworkCallback testNetworkCallback = new TestNetworkCallback(countDownLatch);
+ try {
+ // File a request for wifi network.
+ mConnectivityManager.requestNetwork(
+ new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilitiesProto.TRANSPORT_WIFI)
+ .removeCapability(NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET)
+ .setNetworkSpecifier(specifier)
+ .build(),
+ testNetworkCallback);
+ // Wait for the request to reach the wifi stack before kick-starting the UI
+ // interactions.
+ Thread.sleep(100);
+ // Start the UI interactions.
+ uiThread.run();
+ // now wait for callback
+ assertThat(countDownLatch.await(
+ DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ }
+ if (shouldUserReject) {
+ assertThat(testNetworkCallback.onUnavailableCalled).isTrue();
+ } else {
+ assertThat(testNetworkCallback.onAvailableCalled).isTrue();
+ assertConnectionEquals(
+ network, (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo());
+ }
+ try {
+ // Ensure that the UI interaction thread has completed.
+ uiThread.join(DURATION_UI_INTERACTION_MILLIS);
+ } catch (InterruptedException e) {
+ fail("UI interaction interrupted");
+ }
+ return testNetworkCallback;
+ }
+
+ /**
+ * Returns the number of wifi connections visible at the networking layer.
+ */
+ public long getNumWifiConnections() {
+ Network[] networks = mConnectivityManager.getAllNetworks();
+ return Arrays.stream(networks)
+ .filter(n ->
+ mConnectivityManager.getNetworkCapabilities(n).hasTransport(TRANSPORT_WIFI))
+ .count();
+ }
+}
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 f5fb066..63521c2 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -80,6 +80,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import androidx.core.os.BuildCompat;
@@ -648,6 +649,26 @@
}
}
+ public void testConvertBetweenChannelFrequencyMhz() throws Exception {
+ int[] testFrequency_2G = {2412, 2437, 2462, 2484};
+ int[] testFrequency_5G = {5180, 5220, 5540, 5745};
+ int[] testFrequency_6G = {5955, 6435, 6535, 7115};
+ int[] testFrequency_60G = {58320, 64800};
+ SparseArray<int[]> testData = new SparseArray<>() {{
+ put(ScanResult.WIFI_BAND_24_GHZ, testFrequency_2G);
+ put(ScanResult.WIFI_BAND_5_GHZ, testFrequency_5G);
+ put(ScanResult.WIFI_BAND_6_GHZ, testFrequency_6G);
+ put(ScanResult.WIFI_BAND_60_GHZ, testFrequency_60G);
+ }};
+
+ for (int i = 0; i < testData.size(); i++) {
+ for (int frequency : testData.valueAt(i)) {
+ assertEquals(frequency, ScanResult.convertChannelToFrequencyMhz(
+ ScanResult.convertFrequencyMhzToChannel(frequency), testData.keyAt(i)));
+ }
+ }
+ }
+
// Return true if location is enabled.
private boolean isLocationEnabled() {
return Settings.Secure.getInt(getContext().getContentResolver(),
@@ -924,12 +945,52 @@
}
}
+ private List<Integer> getSupportedSoftApBand(SoftApCapability capability) {
+ List<Integer> supportedApBands = new ArrayList<>();
+ if (mWifiManager.is24GHzBandSupported() &&
+ capability.areFeaturesSupported(
+ SoftApCapability.SOFTAP_FEATURE_BAND_24G_SUPPORTED)) {
+ supportedApBands.add(SoftApConfiguration.BAND_2GHZ);
+ }
+ if (mWifiManager.is5GHzBandSupported() &&
+ capability.areFeaturesSupported(
+ SoftApCapability.SOFTAP_FEATURE_BAND_5G_SUPPORTED)) {
+ supportedApBands.add(SoftApConfiguration.BAND_5GHZ);
+ }
+ if (mWifiManager.is6GHzBandSupported() &&
+ capability.areFeaturesSupported(
+ SoftApCapability.SOFTAP_FEATURE_BAND_6G_SUPPORTED)) {
+ supportedApBands.add(SoftApConfiguration.BAND_6GHZ);
+ }
+ if (mWifiManager.is60GHzBandSupported() &&
+ capability.areFeaturesSupported(
+ SoftApCapability.SOFTAP_FEATURE_BAND_60G_SUPPORTED)) {
+ supportedApBands.add(SoftApConfiguration.BAND_60GHZ);
+ }
+ return supportedApBands;
+ }
+
private TestLocalOnlyHotspotCallback startLocalOnlyHotspot() {
// Location mode must be enabled for this test
if (!isLocationEnabled()) {
fail("Please enable location for this test");
}
+ TestExecutor executor = new TestExecutor();
+ TestSoftApCallback capabilityCallback = new TestSoftApCallback(mLock);
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ List<Integer> supportedSoftApBands = new ArrayList<>();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ verifyRegisterSoftApCallback(executor, capabilityCallback);
+ supportedSoftApBands = getSupportedSoftApBand(
+ capabilityCallback.getCurrentSoftApCapability());
+ } catch (Exception ex) {
+ } finally {
+ // clean up
+ mWifiManager.unregisterSoftApCallback(capabilityCallback);
+ uiAutomation.dropShellPermissionIdentity();
+ }
TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
synchronized (mLock) {
try {
@@ -945,16 +1006,15 @@
assertNotNull(softApConfig);
int securityType = softApConfig.getSecurityType();
if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN
- || securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
- || securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) {
+ || securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
+ || securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) {
assertNotNull(softApConfig.toWifiConfiguration());
} else {
assertNull(softApConfig.toWifiConfiguration());
}
if (!hasAutomotiveFeature()) {
- // TODO: b/179557841 check the supported band to determine the assert band.
- assertEquals(
- SoftApConfiguration.BAND_2GHZ,
+ assertEquals(supportedSoftApBands.size() > 0 ? supportedSoftApBands.get(0)
+ : SoftApConfiguration.BAND_2GHZ,
callback.reservation.getSoftApConfiguration().getBand());
}
assertFalse(callback.onFailedCalled);
@@ -1678,6 +1738,7 @@
// Bssid set dodesn't support for tethered hotspot
SoftApConfiguration currentConfig = mWifiManager.getSoftApConfiguration();
compareSoftApConfiguration(targetConfig, currentConfig);
+ assertTrue(currentConfig.isUserConfiguration());
}
private void compareSoftApConfiguration(SoftApConfiguration currentConfig,
@@ -1704,8 +1765,6 @@
if (BuildCompat.isAtLeastS()) {
assertEquals(currentConfig.getMacRandomizationSetting(),
testSoftApConfig.getMacRandomizationSetting());
- assertTrue(Arrays.equals(currentConfig.getBands(),
- testSoftApConfig.getBands()));
assertEquals(currentConfig.getChannels().toString(),
testSoftApConfig.getChannels().toString());
assertEquals(currentConfig.isBridgedModeOpportunisticShutdownEnabled(),
@@ -1803,15 +1862,14 @@
() -> mWifiManager.isWifiEnabled() == true);
turnOffWifiAndTetheredHotspotIfEnabled();
verifyRegisterSoftApCallback(executor, callback);
-
+ int[] testBands = {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ};
// Test bridged SoftApConfiguration set and get (setBands)
SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
.setSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
- .setBands(new int[] {
- SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ})
+ .setBands(testBands)
.build();
- boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(testSoftApConfig.getBands(),
+ boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(testBands,
callback.getCurrentSoftApCapability());
verifySetGetSoftApConfig(testSoftApConfig);
@@ -2005,6 +2063,10 @@
TestSoftApCallback callback = new TestSoftApCallback(mLock);
try {
uiAutomation.adoptShellPermissionIdentity();
+ // check that tethering is supported by the device
+ if (!mTetheringManager.isTetheringSupported()) {
+ return;
+ }
turnOffWifiAndTetheredHotspotIfEnabled();
verifyRegisterSoftApCallback(executor, callback);
@@ -3233,26 +3295,27 @@
}
/**
- * Verify WifiNetworkSuggestion.Builder.setIsEnhancedMacRandomizationEnabled(true) creates a
- * WifiConfiguration with macRandomizationSetting == RANDOMIZATION_ENHANCED.
+ * Verify WifiNetworkSuggestion.Builder.setMacRandomizationSetting(WifiNetworkSuggestion
+ * .RANDOMIZATION_NON_PERSISTENT) creates a
+ * WifiConfiguration with macRandomizationSetting == RANDOMIZATION_NON_PERSISTENT.
* Then verify by default, a WifiConfiguration created by suggestions should have
* macRandomizationSetting == RANDOMIZATION_PERSISTENT.
* TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
*/
@SdkSuppress(minSdkVersion = 31, codeName = "S")
- public void testSuggestionBuilderEnhancedMacRandomizationSetting() throws Exception {
+ public void testSuggestionBuilderNonPersistentRandomization() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
- .setIsEnhancedMacRandomizationEnabled(true)
+ .setMacRandomizationSetting(WifiNetworkSuggestion.RANDOMIZATION_NON_PERSISTENT)
.build();
assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
- WifiConfiguration.RANDOMIZATION_ENHANCED);
+ WifiConfiguration.RANDOMIZATION_NON_PERSISTENT);
suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
index d819f2c..520d364 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -16,39 +16,25 @@
package android.net.wifi.cts;
-import static android.net.NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
import static android.os.Process.myUid;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
-import static junit.framework.TestCase.assertFalse;
-
-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.Context;
import android.net.ConnectivityManager;
-import android.net.LinkProperties;
import android.net.MacAddress;
-import android.net.Network;
-import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.NetworkRequestMatchCallback;
import android.net.wifi.WifiNetworkSpecifier;
import android.os.PatternMatcher;
-import android.os.WorkSource;
import android.platform.test.annotations.AppModeFull;
import android.support.test.uiautomator.UiDevice;
-import android.text.TextUtils;
import androidx.core.os.BuildCompat;
import androidx.test.filters.SmallTest;
@@ -57,7 +43,6 @@
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.AfterClass;
@@ -77,7 +62,6 @@
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.List;
-import java.util.concurrent.Executors;
/**
* Tests the entire connection flow using {@link WifiNetworkSpecifier} embedded in a
@@ -192,16 +176,11 @@
private WifiManager mWifiManager;
private ConnectivityManager mConnectivityManager;
private UiDevice mUiDevice;
- private final Object mLock = new Object();
- private final Object mUiLock = new Object();
private WifiConfiguration mTestNetwork;
- private TestNetworkCallback mNetworkCallback;
+ private ConnectivityManager.NetworkCallback mNrNetworkCallback;
+ private TestHelper mTestHelper;
private static final int DURATION = 10_000;
- private static final int DURATION_UI_INTERACTION = 25_000;
- private static final int DURATION_NETWORK_CONNECTION = 60_000;
- private static final int DURATION_SCREEN_TOGGLE = 2000;
- private static final int SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID = 3;
@BeforeClass
public static void setUpClass() throws Exception {
@@ -210,7 +189,7 @@
if (!WifiFeature.isWifiSupported(context)) return;
WifiManager wifiManager = context.getSystemService(WifiManager.class);
- assertNotNull(wifiManager);
+ assertThat(wifiManager).isNotNull();
// turn on verbose logging for tests
sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
@@ -224,13 +203,16 @@
() -> wifiManager.setScanThrottleEnabled(false));
// enable Wifi
- if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+ if (!wifiManager.isWifiEnabled()) {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+ }
PollingCheck.check("Wifi not enabled", DURATION, () -> wifiManager.isWifiEnabled());
// check we have >= 1 saved network
List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
() -> wifiManager.getPrivilegedConfiguredNetworks());
- assertFalse("Need at least one saved network", savedNetworks.isEmpty());
+ assertWithMessage("Need at least one saved network")
+ .that(savedNetworks.isEmpty()).isFalse();
// Disconnect & disable auto-join on the saved network to prevent auto-connect from
// interfering with the test.
@@ -248,9 +230,11 @@
if (!WifiFeature.isWifiSupported(context)) return;
WifiManager wifiManager = context.getSystemService(WifiManager.class);
- assertNotNull(wifiManager);
+ assertThat(wifiManager).isNotNull();
- if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+ if (!wifiManager.isWifiEnabled()) {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+ }
// Re-enable networks.
ShellIdentityUtils.invokeWithShellPermissions(
@@ -271,11 +255,12 @@
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mTestHelper = new TestHelper(mContext, mUiDevice);
assumeTrue(WifiFeature.isWifiSupported(mContext));
// turn screen on
- turnScreenOn();
+ mTestHelper.turnScreenOn();
// Clear any existing app state before each test.
if (BuildCompat.isAtLeastS()) {
@@ -285,8 +270,9 @@
List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.getPrivilegedConfiguredNetworks());
- // Pick the last saved network on the device (assumes that it is in range)
- mTestNetwork = savedNetworks.get(savedNetworks.size() - 1);
+ // Pick any network in range.
+ mTestNetwork = TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks)
+ .get(0);
// Wait for Wifi to be disconnected.
PollingCheck.check(
@@ -298,261 +284,25 @@
@After
public void tearDown() throws Exception {
// If there is failure, ensure we unregister the previous request.
- if (mNetworkCallback != null) {
- mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+ if (mNrNetworkCallback != null) {
+ mConnectivityManager.unregisterNetworkCallback(mNrNetworkCallback);
}
// Clear any existing app state after each test.
if (BuildCompat.isAtLeastS()) {
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
}
- turnScreenOff();
- }
-
- private static void setWifiEnabled(boolean enable) throws Exception {
- // now trigger the change using shell commands.
- SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
- }
-
- private void turnScreenOn() throws Exception {
- mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
- mUiDevice.executeShellCommand("wm dismiss-keyguard");
- // Since the screen on/off intent is ordered, they will not be sent right now.
- Thread.sleep(DURATION_SCREEN_TOGGLE);
- }
-
- private void turnScreenOff() throws Exception {
- mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
- // Since the screen on/off intent is ordered, they will not be sent right now.
- Thread.sleep(DURATION_SCREEN_TOGGLE);
- }
-
- private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
- private final Object mLock;
- public boolean onAvailableCalled = false;
- public boolean onUnavailableCalled = false;
- public NetworkCapabilities networkCapabilities;
-
- TestNetworkCallback(Object lock) {
- mLock = lock;
- }
-
- @Override
- public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
- LinkProperties linkProperties, boolean blocked) {
- synchronized (mLock) {
- onAvailableCalled = true;
- this.networkCapabilities = networkCapabilities;
- mLock.notify();
- }
- }
-
- @Override
- public void onUnavailable() {
- synchronized (mLock) {
- onUnavailableCalled = true;
- mLock.notify();
- }
- }
- }
-
- private static class TestNetworkRequestMatchCallback implements NetworkRequestMatchCallback {
- private final Object mLock;
-
- public boolean onRegistrationCalled = false;
- public boolean onAbortCalled = false;
- public boolean onMatchCalled = false;
- public boolean onConnectSuccessCalled = false;
- public boolean onConnectFailureCalled = false;
- public WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback = null;
- public List<ScanResult> matchedScanResults = null;
-
- TestNetworkRequestMatchCallback(Object lock) {
- mLock = lock;
- }
-
- @Override
- public void onUserSelectionCallbackRegistration(
- WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback) {
- synchronized (mLock) {
- onRegistrationCalled = true;
- this.userSelectionCallback = userSelectionCallback;
- mLock.notify();
- }
- }
-
- @Override
- public void onAbort() {
- synchronized (mLock) {
- onAbortCalled = true;
- mLock.notify();
- }
- }
-
- @Override
- public void onMatch(List<ScanResult> scanResults) {
- synchronized (mLock) {
- // This can be invoked multiple times. So, ignore after the first one to avoid
- // disturbing the rest of the test sequence.
- if (onMatchCalled) return;
- onMatchCalled = true;
- matchedScanResults = scanResults;
- mLock.notify();
- }
- }
-
- @Override
- public void onUserSelectionConnectSuccess(WifiConfiguration config) {
- synchronized (mLock) {
- onConnectSuccessCalled = true;
- mLock.notify();
- }
- }
-
- @Override
- public void onUserSelectionConnectFailure(WifiConfiguration config) {
- synchronized (mLock) {
- onConnectFailureCalled = true;
- mLock.notify();
- }
- }
- }
-
- private void handleUiInteractions(boolean shouldUserReject) {
- UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- TestNetworkRequestMatchCallback networkRequestMatchCallback =
- new TestNetworkRequestMatchCallback(mUiLock);
- try {
- uiAutomation.adoptShellPermissionIdentity();
-
- // 1. Wait for registration callback.
- synchronized (mUiLock) {
- try {
- mWifiManager.registerNetworkRequestMatchCallback(
- Executors.newSingleThreadExecutor(), networkRequestMatchCallback);
- mUiLock.wait(DURATION_UI_INTERACTION);
- } catch (InterruptedException e) {
- }
- }
- assertTrue(networkRequestMatchCallback.onRegistrationCalled);
- assertNotNull(networkRequestMatchCallback.userSelectionCallback);
-
- // 2. Wait for matching scan results
- synchronized (mUiLock) {
- try {
- mUiLock.wait(DURATION_UI_INTERACTION);
- } catch (InterruptedException e) {
- }
- }
- assertTrue(networkRequestMatchCallback.onMatchCalled);
- assertNotNull(networkRequestMatchCallback.matchedScanResults);
- assertThat(networkRequestMatchCallback.matchedScanResults.size()).isAtLeast(1);
-
- // 3. Trigger connection to one of the matched networks or reject the request.
- if (shouldUserReject) {
- networkRequestMatchCallback.userSelectionCallback.reject();
- } else {
- networkRequestMatchCallback.userSelectionCallback.select(mTestNetwork);
- }
-
- // 4. Wait for connection success or abort.
- synchronized (mUiLock) {
- try {
- mUiLock.wait(DURATION_UI_INTERACTION);
- } catch (InterruptedException e) {
- }
- }
- if (shouldUserReject) {
- assertTrue(networkRequestMatchCallback.onAbortCalled);
- } else {
- assertTrue(networkRequestMatchCallback.onConnectSuccessCalled);
- }
- } finally {
- mWifiManager.unregisterNetworkRequestMatchCallback(networkRequestMatchCallback);
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
- /**
- * Tests the entire connection flow using the provided specifier.
- *
- * @param specifier Specifier to use for network request.
- * @param shouldUserReject Whether to simulate user rejection or not.
- */
- private void testConnectionFlowWithSpecifier(
- WifiNetworkSpecifier specifier, boolean shouldUserReject) {
- // Fork a thread to handle the UI interactions.
- Thread uiThread = new Thread(() -> handleUiInteractions(shouldUserReject));
-
- // File the network request & wait for the callback.
- mNetworkCallback = new TestNetworkCallback(mLock);
- synchronized (mLock) {
- try {
- // File a request for wifi network.
- mConnectivityManager.requestNetwork(
- new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_WIFI)
- .removeCapability(NET_CAPABILITY_INTERNET)
- .setNetworkSpecifier(specifier)
- .build(),
- mNetworkCallback);
- // Wait for the request to reach the wifi stack before kick-starting the UI
- // interactions.
- Thread.sleep(100);
- // Start the UI interactions.
- uiThread.run();
- // now wait for callback
- mLock.wait(DURATION_NETWORK_CONNECTION);
- } catch (InterruptedException e) {
- }
- }
- if (shouldUserReject) {
- assertTrue(mNetworkCallback.onUnavailableCalled);
- } else {
- assertTrue(mNetworkCallback.onAvailableCalled);
- }
-
- try {
- // Ensure that the UI interaction thread has completed.
- uiThread.join(DURATION_UI_INTERACTION);
- } catch (InterruptedException e) {
- fail("UI interaction interrupted");
- }
-
- // Release the request after the test.
- mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
- mNetworkCallback = null;
+ mTestHelper.turnScreenOff();
}
private void testSuccessfulConnectionWithSpecifier(WifiNetworkSpecifier specifier) {
- testConnectionFlowWithSpecifier(specifier, false);
+ mNrNetworkCallback = mTestHelper.testConnectionFlowWithSpecifier(
+ mTestNetwork, specifier, false);
}
private void testUserRejectionWithSpecifier(WifiNetworkSpecifier specifier) {
- testConnectionFlowWithSpecifier(specifier, true);
- }
-
- private static String removeDoubleQuotes(String string) {
- return WifiInfo.sanitizeSsid(string);
- }
-
- private WifiNetworkSpecifier.Builder createSpecifierBuilderWithCredentialFromSavedNetwork() {
- WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder();
- if (mTestNetwork.preSharedKey != null) {
- if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
- specifierBuilder.setWpa2Passphrase(removeDoubleQuotes(mTestNetwork.preSharedKey));
- } else if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
- specifierBuilder.setWpa3Passphrase(removeDoubleQuotes(mTestNetwork.preSharedKey));
- } else {
- fail("Unsupported security type found in saved networks");
- }
- } else if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
- specifierBuilder.setIsEnhancedOpen(true);
- } else if (!mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
- fail("Unsupported security type found in saved networks");
- }
- specifierBuilder.setIsHiddenSsid(mTestNetwork.hiddenSSID);
- return specifierBuilder;
+ mNrNetworkCallback = mTestHelper.testConnectionFlowWithSpecifier(
+ mTestNetwork, specifier, true);
}
/**
@@ -560,8 +310,9 @@
*/
@Test
public void testConnectionWithSpecificSsid() {
- WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
- .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+ WifiNetworkSpecifier specifier =
+ TestHelper.createSpecifierBuilderWithCredentialFromSavedNetwork(
+ mTestNetwork)
.build();
testSuccessfulConnectionWithSpecifier(specifier);
}
@@ -573,79 +324,28 @@
public void testConnectionWithSsidPattern() {
// Creates a ssid pattern by dropping the last char in the saved network & pass that
// as a prefix match pattern in the request.
- String ssidUnquoted = removeDoubleQuotes(mTestNetwork.SSID);
+ String ssidUnquoted = WifiInfo.sanitizeSsid(mTestNetwork.SSID);
assertThat(ssidUnquoted.length()).isAtLeast(2);
String ssidPrefix = ssidUnquoted.substring(0, ssidUnquoted.length() - 1);
// Note: The match may return more than 1 network in this case since we use a prefix match,
// But, we will still ensure that the UI interactions in the test still selects the
// saved network for connection.
- WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
+ WifiNetworkSpecifier specifier =
+ TestHelper.createSpecifierBuilderWithCredentialFromSavedNetwork(mTestNetwork)
.setSsidPattern(new PatternMatcher(ssidPrefix, PatternMatcher.PATTERN_PREFIX))
.build();
testSuccessfulConnectionWithSpecifier(specifier);
}
- private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
- private final Object mLock;
- public boolean onAvailableCalled = false;
-
- TestScanResultsCallback(Object lock) {
- mLock = lock;
- }
-
- @Override
- public void onScanResultsAvailable() {
- synchronized (mLock) {
- onAvailableCalled = true;
- mLock.notify();
- }
- }
- }
-
- /**
- * Loops through all available scan results and finds the first match for the saved network.
- *
- * 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.
- */
- private ScanResult findScanResultMatchingSavedNetwork() {
- 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(mLock);
- synchronized (mLock) {
- try {
- mWifiManager.registerScanResultsCallback(
- Executors.newSingleThreadExecutor(), scanResultsCallback);
- mWifiManager.startScan(new WorkSource(myUid()));
- // now wait for callback
- mLock.wait(DURATION_NETWORK_CONNECTION);
- } catch (InterruptedException e) {
- } finally {
- mWifiManager.unregisterScanResultsCallback(scanResultsCallback);
- }
- }
- List<ScanResult> scanResults = mWifiManager.getScanResults();
- if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
- for (ScanResult scanResult : scanResults) {
- if (TextUtils.equals(scanResult.SSID, removeDoubleQuotes(mTestNetwork.SSID))) {
- return scanResult;
- }
- }
- }
- fail("No matching scan results found");
- return null;
- }
-
/**
* Tests the entire connection flow using a specific BSSID in the specifier.
*/
@Test
public void testConnectionWithSpecificBssid() {
- ScanResult scanResult = findScanResultMatchingSavedNetwork();
- WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
- .setBssid(MacAddress.fromString(scanResult.BSSID))
- .build();
+ WifiNetworkSpecifier specifier =
+ TestHelper.createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetwork)
+ .build();
testSuccessfulConnectionWithSpecifier(specifier);
}
@@ -654,14 +354,15 @@
*/
@Test
public void testConnectionWithBssidPattern() {
- ScanResult scanResult = findScanResultMatchingSavedNetwork();
// Note: The match may return more than 1 network in this case since we use a prefix match,
// But, we will still ensure that the UI interactions in the test still selects the
// saved network for connection.
- WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
- .setBssidPattern(MacAddress.fromString(scanResult.BSSID),
- MacAddress.fromString("ff:ff:ff:00:00:00"))
- .build();
+ WifiNetworkSpecifier specifier =
+ TestHelper.createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetwork)
+ .setBssidPattern(MacAddress.fromString(mTestNetwork.BSSID),
+ MacAddress.fromString("ff:ff:ff:00:00:00"))
+ .build();
testSuccessfulConnectionWithSpecifier(specifier);
}
@@ -670,9 +371,10 @@
*/
@Test
public void testUserRejectionWithSpecificSsid() {
- WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
- .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
- .build();
+ WifiNetworkSpecifier specifier =
+ TestHelper.createSpecifierBuilderWithCredentialFromSavedNetwork(
+ mTestNetwork)
+ .build();
testUserRejectionWithSpecifier(specifier);
}
@@ -683,11 +385,11 @@
@Test
public void testBuilderForWpa2Enterprise() {
WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
- .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+ .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
.setWpa2EnterpriseConfig(new WifiEnterpriseConfig())
.build();
WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
- .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+ .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
.setWpa2EnterpriseConfig(new WifiEnterpriseConfig())
.build();
assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
@@ -700,11 +402,11 @@
@Test
public void testBuilderForWpa3Enterprise() {
WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
- .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+ .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
.setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
.build();
WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
- .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+ .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
.setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
.build();
assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
@@ -717,11 +419,11 @@
@Test
public void testBuilderForWpa3EnterpriseWithStandardApi() {
WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
- .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+ .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
.setWpa3EnterpriseStandardModeConfig(new WifiEnterpriseConfig())
.build();
WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
- .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+ .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
.setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
.build();
assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
@@ -741,11 +443,11 @@
enterpriseConfig.setAltSubjectMatch("domain.com");
WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
- .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+ .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
.setWpa3Enterprise192BitModeConfig(enterpriseConfig)
.build();
WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
- .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+ .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
.setWpa3Enterprise192BitModeConfig(enterpriseConfig)
.build();
assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
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 8e21d3e..acfc16d 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
@@ -16,20 +16,54 @@
package android.net.wifi.cts;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.wifi.WifiEnterpriseConfig.Eap.AKA;
import static android.net.wifi.WifiEnterpriseConfig.Eap.WAPI_CERT;
+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;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
import android.net.MacAddress;
+import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkSuggestion;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSp;
import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
import android.telephony.TelephonyManager;
import androidx.core.os.BuildCompat;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+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.io.ByteArrayInputStream;
import java.io.InputStream;
@@ -41,10 +75,16 @@
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
@SmallTest
-public class WifiNetworkSuggestionTest extends WifiJUnit3TestBase {
+@RunWith(AndroidJUnit4.class)
+public class WifiNetworkSuggestionTest extends WifiJUnit4TestBase {
+ private static final String TAG = "WifiNetworkSuggestionTest";
+
private static final String TEST_SSID = "testSsid";
private static final String TEST_BSSID = "00:df:aa:bc:12:23";
private static final String TEST_PASSPHRASE = "testPassword";
@@ -52,23 +92,134 @@
private static final int TEST_PRIORITY_GROUP = 1;
private static final int TEST_SUB_ID = 1;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
+ private static boolean sWasVerboseLoggingEnabled;
+ private static boolean sWasScanThrottleEnabled;
+ private static boolean sWasWifiEnabled;
+
+ private Context mContext;
+ private WifiManager mWifiManager;
+ private ConnectivityManager mConnectivityManager;
+ private UiDevice mUiDevice;
+ private WifiConfiguration mTestNetwork;
+ private ConnectivityManager.NetworkCallback mNsNetworkCallback;
+ private ScheduledExecutorService mExecutorService;
+ private TestHelper mTestHelper;
+
+ private static final int DURATION_MILLIS = 10_000;
+
+ @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;
+
+ 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
+ sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.isWifiEnabled());
+ if (!wifiManager.isWifiEnabled()) {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
}
+ PollingCheck.check("Wifi not enabled", DURATION_MILLIS, () -> wifiManager.isWifiEnabled());
}
- @Override
- protected void tearDown() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- super.tearDown();
- return;
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ if (!WifiFeature.isWifiSupported(context)) return;
+
+ 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 {
+ 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(WifiFeature.isWifiSupported(mContext));
+ // skip the test if location is not supported
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION));
+
+ assertWithMessage("Please enable location for this test!").that(
+ mContext.getSystemService(LocationManager.class).isLocationEnabled()).isTrue();
+
+ // turn screen on
+ mTestHelper.turnScreenOn();
+
+ // Clear any existing app state before each test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+
+ // check we have >= 1 saved network
+ List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.getPrivilegedConfiguredNetworks());
+ assertFalse("Need at least one saved network", savedNetworks.isEmpty());
+ // Pick any network in range.
+ mTestNetwork = TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks)
+ .get(0);
+
+ // Disconnect & disable auto-join on the saved network to prevent auto-connect from
+ // interfering with the test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ for (WifiConfiguration savedNetwork : savedNetworks) {
+ mWifiManager.disableNetwork(savedNetwork.networkId);
+ }
+ mWifiManager.disconnect();
+ });
+
+ // Wait for Wifi to be disconnected.
+ PollingCheck.check(
+ "Wifi not disconnected",
+ 20_000,
+ () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Re-enable networks.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
+ mWifiManager.enableNetwork(savedNetwork.networkId, false);
+ }
+ });
+ // Release the requests after the test.
+ if (mNsNetworkCallback != null) {
+ mConnectivityManager.unregisterNetworkCallback(mNsNetworkCallback);
}
- super.tearDown();
+ mExecutorService.shutdownNow();
+ // Clear any existing app state after each test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+ mTestHelper.turnScreenOff();
}
private static final String CA_SUITE_B_RSA3072_CERT_STRING =
@@ -99,7 +250,7 @@
+ "27HHbC0JXjNvlm2DLp23v4NTxS7WZGYsxyUj5DZrxBxqCsTXu/01w1BrQKWKh9FM\n"
+ "aVrlA8omfVODK2CSuw+KhEMHepRv/AUgsLl4L4+RMoa+\n"
+ "-----END CERTIFICATE-----\n";
- public static final X509Certificate CA_SUITE_B_RSA3072_CERT =
+ private static final X509Certificate CA_SUITE_B_RSA3072_CERT =
loadCertificate(CA_SUITE_B_RSA3072_CERT_STRING);
private static final String CA_SUITE_B_ECDSA_CERT_STRING =
@@ -118,7 +269,7 @@
+ "5soIeLVYc8bPYN1pbhXW1gIxALdEe2sh03nBHyQH4adYoZungoCwt8mp/7sJFxou\n"
+ "9UnRegyBgGzf74ROWdpZHzh+Pg==\n"
+ "-----END CERTIFICATE-----\n";
- public static final X509Certificate CA_SUITE_B_ECDSA_CERT =
+ private static final X509Certificate CA_SUITE_B_ECDSA_CERT =
loadCertificate(CA_SUITE_B_ECDSA_CERT_STRING);
private static final String CLIENT_SUITE_B_RSA3072_CERT_STRING =
@@ -147,7 +298,7 @@
+ "HVVcP7U8RgqrbIjL1QgHU4KBhGi+WSUh/mRplUCNvHgcYdcHi/gHpj/j6ubwqIGV\n"
+ "z4iSolAHYTmBWcLyE0NgpzE6ntp+53r2KaUJA99l2iGVzbWTwqPSm0XAVw==\n"
+ "-----END CERTIFICATE-----\n";
- public static final X509Certificate CLIENT_SUITE_B_RSA3072_CERT =
+ private static final X509Certificate CLIENT_SUITE_B_RSA3072_CERT =
loadCertificate(CLIENT_SUITE_B_RSA3072_CERT_STRING);
private static final byte[] CLIENT_SUITE_B_RSA3072_KEY_DATA = new byte[]{
@@ -451,7 +602,7 @@
(byte) 0x93, (byte) 0x9e, (byte) 0x62, (byte) 0x94, (byte) 0xc6, (byte) 0x07,
(byte) 0x83, (byte) 0x96, (byte) 0xd6, (byte) 0x27, (byte) 0xa6, (byte) 0xd8
};
- public static final PrivateKey CLIENT_SUITE_B_RSA3072_KEY =
+ private static final PrivateKey CLIENT_SUITE_B_RSA3072_KEY =
loadPrivateKey("RSA", CLIENT_SUITE_B_RSA3072_KEY_DATA);
private static final String CLIENT_SUITE_B_ECDSA_CERT_STRING =
@@ -468,7 +619,7 @@
+ "ueJmP87KF92/thhoQ9OrRo8uJITPmNDswwIwP2Q1AZCSL4BI9dYrqu07Ar+pSkXE\n"
+ "R7oOqGdZR+d/MvXcFSrbIaLKEoHXmQamIHLe\n"
+ "-----END CERTIFICATE-----\n";
- public static final X509Certificate CLIENT_SUITE_B_ECDSA_CERT =
+ private static final X509Certificate CLIENT_SUITE_B_ECDSA_CERT =
loadCertificate(CLIENT_SUITE_B_ECDSA_CERT_STRING);
private static final byte[] CLIENT_SUITE_B_ECC_KEY_DATA = new byte[]{
@@ -504,7 +655,7 @@
(byte) 0x4c, (byte) 0x8b, (byte) 0xe1, (byte) 0x93, (byte) 0x42, (byte) 0x0d,
(byte) 0x67, (byte) 0x1d, (byte) 0xc3, (byte) 0xa2, (byte) 0xeb
};
- public static final PrivateKey CLIENT_SUITE_B_ECC_KEY =
+ private static final PrivateKey CLIENT_SUITE_B_ECC_KEY =
loadPrivateKey("EC", CLIENT_SUITE_B_ECC_KEY_DATA);
private static X509Certificate loadCertificate(String blob) {
@@ -588,11 +739,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWpa2Passphrase() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiNetworkSuggestion suggestion =
createBuilderWithCommonParams()
.setWpa2Passphrase(TEST_PASSPHRASE)
@@ -606,11 +754,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWpa3Passphrase() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiNetworkSuggestion suggestion =
createBuilderWithCommonParams()
.setWpa3Passphrase(TEST_PASSPHRASE)
@@ -624,11 +769,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWapiPassphrase() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiNetworkSuggestion suggestion =
createBuilderWithCommonParams()
.setWapiPassphrase(TEST_PASSPHRASE)
@@ -648,11 +790,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWpa2Enterprise() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiEnterpriseConfig enterpriseConfig = createEnterpriseConfig();
WifiNetworkSuggestion suggestion =
createBuilderWithCommonParams()
@@ -669,11 +808,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWpa3Enterprise() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiEnterpriseConfig enterpriseConfig = createEnterpriseConfig();
WifiNetworkSuggestion suggestion =
createBuilderWithCommonParams()
@@ -690,11 +826,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWpa3EnterpriseWithStandardApi() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiEnterpriseConfig enterpriseConfig = createEnterpriseConfig();
WifiNetworkSuggestion suggestion =
createBuilderWithCommonParams()
@@ -711,11 +844,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWpa3EnterpriseWithSuiteBRsaCerts() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
enterpriseConfig.setCaCertificate(CA_SUITE_B_RSA3072_CERT);
@@ -737,11 +867,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWpa3EnterpriseWithSuiteBEccCerts() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
@@ -763,11 +890,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWpa3Enterprise192bitWithSuiteBRsaCerts() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
enterpriseConfig.setCaCertificate(CA_SUITE_B_RSA3072_CERT);
@@ -789,11 +913,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWpa3Enterprise192bitWithSuiteBEccCerts() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
@@ -815,11 +936,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithWapiEnterprise() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
enterpriseConfig.setEapMethod(WAPI_CERT);
WifiNetworkSuggestion suggestion =
@@ -863,11 +981,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithPasspointConfig() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
PasspointConfiguration passpointConfig = createPasspointConfig();
WifiNetworkSuggestion suggestion =
createBuilderWithCommonParams(true)
@@ -882,10 +997,8 @@
/**
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
*/
+ @Test
public void testBuilderWithCarrierMergedNetwork() throws Exception {
- if (!(WifiFeature.isWifiSupported(getContext()) && BuildCompat.isAtLeastS())) {
- return;
- }
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
@@ -905,11 +1018,8 @@
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class with non enterprise
* network will fail.
*/
+ @Test
public void testBuilderWithCarrierMergedNetworkWithNonEnterpriseNetwork() throws Exception {
- if (!(WifiFeature.isWifiSupported(getContext()) && BuildCompat.isAtLeastS())) {
- return;
- }
-
try {
createBuilderWithCommonParams()
.setWpa2Passphrase(TEST_PASSPHRASE)
@@ -926,10 +1036,8 @@
* Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class with unmetered network
* will fail.
*/
+ @Test
public void testBuilderWithCarrierMergedNetworkWithUnmeteredNetwork() throws Exception {
- if (!(WifiFeature.isWifiSupported(getContext()) && BuildCompat.isAtLeastS())) {
- return;
- }
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
@@ -948,4 +1056,121 @@
fail("Did not receive expected IllegalStateException when tried to build a carrier merged "
+ "network suggestion with unmetered config");
}
+
+ /**
+ * Connect to a network using suggestion API.
+ */
+ @Test
+ public void testConnectToSuggestion() throws Exception {
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetwork)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+ mTestNetwork, suggestion, mExecutorService, null /* restrictedNetworkCapability */);
+ }
+
+ /**
+ * Connect to a network using restricted suggestion API.
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ @Test
+ public void testConnectToOemPaidSuggestion() throws Exception {
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetwork)
+ .setOemPaid(true)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+ mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PAID);
+ }
+
+ /**
+ * Connect to a network using restricted suggestion API.
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ @Test
+ public void testConnectToOemPrivateSuggestion() throws Exception {
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetwork)
+ .setOemPrivate(true)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
+ mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PRIVATE);
+ }
+
+ /**
+ * Simulate connection failure to a network using restricted suggestion API & different net
+ * capability (need corresponding net capability requested for platform to connect).
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ @Test
+ public void testConnectToOemPaidSuggestionFailure() throws Exception {
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetwork)
+ .setOemPaid(true)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+ mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PRIVATE);
+ }
+
+ /**
+ * Simulate connection failure to a network using restricted suggestion API & different net
+ * capability (need corresponding net capability requested for platform to connect).
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ @Test
+ public void testConnectToOemPrivateSuggestionFailure() throws Exception {
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetwork)
+ .setOemPrivate(true)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+ mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PAID);
+ }
+
+ /**
+ * Simulate connection failure to a restricted network using suggestion API & restricted net
+ * capability (need corresponding restricted bit set in suggestion for platform to connect).
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ @Test
+ public void testConnectSuggestionFailureWithOemPaidNetCapability() throws Exception {
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetwork)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+ mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PAID);
+ }
+
+ /**
+ * Simulate connection failure to a restricted network using suggestion API & restricted net
+ * capability (need corresponding restricted bit set in suggestion for platform to connect).
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ @Test
+ public void testConnectSuggestionFailureWithOemPrivateNetCapability() throws Exception {
+ WifiNetworkSuggestion suggestion =
+ TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+ mTestNetwork)
+ .build();
+ mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
+ mTestNetwork, suggestion, mExecutorService, NET_CAPABILITY_OEM_PRIVATE);
+ }
}
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp b/tests/uwb/Android.bp
similarity index 62%
copy from hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
copy to tests/uwb/Android.bp
index 72e5fef..1ecbf76 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
+++ b/tests/uwb/Android.bp
@@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,20 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-java_test_host {
- name: "CtsInstalledLoadingProgressHostTests",
+android_test {
+ name: "CtsUwbTestCases",
defaults: ["cts_defaults"],
- srcs: ["src/**/*.java"],
- libs: [
- "cts-tradefed",
- "tradefed",
- "compatibility-host-util",
- "cts-host-utils",
- ],
- static_libs: ["cts-install-lib-host"],
+ // Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
+ libs: ["android.test.runner"],
+ static_libs: [
+ "androidx.test.ext.junit",
+ "ctstestrunner-axt",
+ "compatibility-device-util-axt",
+ "mockito-target-minus-junit4",
+ ],
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
}
-
diff --git a/tests/uwb/AndroidManifest.xml b/tests/uwb/AndroidManifest.xml
new file mode 100644
index 0000000..adecade
--- /dev/null
+++ b/tests/uwb/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.uwb.cts">
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for android.uwb"
+ android:targetPackage="android.uwb.cts" >
+ </instrumentation>
+</manifest>
+
diff --git a/tests/uwb/AndroidTest.xml b/tests/uwb/AndroidTest.xml
new file mode 100644
index 0000000..f0bb20a
--- /dev/null
+++ b/tests/uwb/AndroidTest.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.
+-->
+<configuration description="Config for CTS UWB 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" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsUwbTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.uwb.cts" />
+ </test>
+</configuration>
diff --git a/tests/uwb/OWNERS b/tests/uwb/OWNERS
new file mode 100644
index 0000000..7ba57cf
--- /dev/null
+++ b/tests/uwb/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 898555
+bstack@google.com
+eliptus@google.com
+jsolnit@google.com
+zachoverflow@google.com
diff --git a/tests/uwb/src/android/uwb/cts/AngleMeasurementTest.java b/tests/uwb/src/android/uwb/cts/AngleMeasurementTest.java
new file mode 100644
index 0000000..c6ddead
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/AngleMeasurementTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.uwb.AngleMeasurement;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link AngleMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AngleMeasurementTest {
+ @Test
+ public void testBuilder() {
+ double radians = 0.1234;
+ double errorRadians = 0.5678;
+ double confidence = 0.5;
+
+ AngleMeasurement.Builder builder = new AngleMeasurement.Builder();
+ tryBuild(builder, false);
+
+ builder.setRadians(radians);
+ tryBuild(builder, false);
+
+ builder.setErrorRadians(errorRadians);
+ tryBuild(builder, false);
+
+ builder.setConfidenceLevel(confidence);
+ AngleMeasurement measurement = tryBuild(builder, true);
+
+ assertEquals(measurement.getRadians(), radians, 0);
+ assertEquals(measurement.getErrorRadians(), errorRadians, 0);
+ assertEquals(measurement.getConfidenceLevel(), confidence, 0);
+ }
+
+ private AngleMeasurement tryBuild(AngleMeasurement.Builder builder, boolean expectSuccess) {
+ AngleMeasurement measurement = null;
+ try {
+ measurement = builder.build();
+ if (!expectSuccess) {
+ fail("Expected AngleMeasurement.Builder.build() to fail, but it succeeded");
+ }
+ } catch (IllegalStateException e) {
+ if (expectSuccess) {
+ fail("Expected AngleMeasurement.Builder.build() to succeed, but it failed");
+ }
+ }
+ return measurement;
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ AngleMeasurement measurement = UwbTestUtils.getAngleMeasurement();
+ measurement.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ AngleMeasurement fromParcel = AngleMeasurement.CREATOR.createFromParcel(parcel);
+ assertEquals(measurement, fromParcel);
+ }
+}
diff --git a/tests/uwb/src/android/uwb/cts/AngleOfArrivalMeasurementTest.java b/tests/uwb/src/android/uwb/cts/AngleOfArrivalMeasurementTest.java
new file mode 100644
index 0000000..16dc4c2
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/AngleOfArrivalMeasurementTest.java
@@ -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.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.uwb.AngleMeasurement;
+import android.uwb.AngleOfArrivalMeasurement;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link AngleOfArrivalMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AngleOfArrivalMeasurementTest {
+
+ @Test
+ public void testBuilder() {
+ AngleMeasurement azimuth = UwbTestUtils.getAngleMeasurement();
+ AngleMeasurement altitude = UwbTestUtils.getAngleMeasurement();
+
+ AngleOfArrivalMeasurement.Builder builder = new AngleOfArrivalMeasurement.Builder();
+ tryBuild(builder, false);
+
+ builder.setAltitude(altitude);
+ tryBuild(builder, false);
+
+ builder.setAzimuth(azimuth);
+ AngleOfArrivalMeasurement measurement = tryBuild(builder, true);
+
+ assertEquals(azimuth, measurement.getAzimuth());
+ assertEquals(altitude, measurement.getAltitude());
+ }
+
+ private AngleMeasurement getAngleMeasurement(double radian, double error, double confidence) {
+ return new AngleMeasurement.Builder()
+ .setRadians(radian)
+ .setErrorRadians(error)
+ .setConfidenceLevel(confidence)
+ .build();
+ }
+
+ private AngleOfArrivalMeasurement tryBuild(AngleOfArrivalMeasurement.Builder builder,
+ boolean expectSuccess) {
+ AngleOfArrivalMeasurement measurement = null;
+ try {
+ measurement = builder.build();
+ if (!expectSuccess) {
+ fail("Expected AngleOfArrivalMeasurement.Builder.build() to fail");
+ }
+ } catch (IllegalStateException e) {
+ if (expectSuccess) {
+ fail("Expected AngleOfArrivalMeasurement.Builder.build() to succeed");
+ }
+ }
+ return measurement;
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ AngleOfArrivalMeasurement measurement = UwbTestUtils.getAngleOfArrivalMeasurement();
+ measurement.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ AngleOfArrivalMeasurement fromParcel =
+ AngleOfArrivalMeasurement.CREATOR.createFromParcel(parcel);
+ assertEquals(measurement, fromParcel);
+ }
+}
diff --git a/tests/uwb/src/android/uwb/cts/DistanceMeasurementTest.java b/tests/uwb/src/android/uwb/cts/DistanceMeasurementTest.java
new file mode 100644
index 0000000..fdebc78
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/DistanceMeasurementTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.uwb.DistanceMeasurement;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link DistanceMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DistanceMeasurementTest {
+ private static final double EPSILON = 0.00000000001;
+
+ @Test
+ public void testBuilder() {
+ double meters = 0.12;
+ double error = 0.54;
+ double confidence = 0.99;
+
+ DistanceMeasurement.Builder builder = new DistanceMeasurement.Builder();
+ tryBuild(builder, false);
+
+ builder.setMeters(meters);
+ tryBuild(builder, false);
+
+ builder.setErrorMeters(error);
+ tryBuild(builder, false);
+
+ builder.setConfidenceLevel(confidence);
+ DistanceMeasurement measurement = tryBuild(builder, true);
+
+ assertEquals(meters, measurement.getMeters(), 0);
+ assertEquals(error, measurement.getErrorMeters(), 0);
+ assertEquals(confidence, measurement.getConfidenceLevel(), 0);
+ }
+
+ private DistanceMeasurement tryBuild(DistanceMeasurement.Builder builder,
+ boolean expectSuccess) {
+ DistanceMeasurement measurement = null;
+ try {
+ measurement = builder.build();
+ if (!expectSuccess) {
+ fail("Expected DistanceMeasurement.Builder.build() to fail");
+ }
+ } catch (IllegalStateException e) {
+ if (expectSuccess) {
+ fail("Expected DistanceMeasurement.Builder.build() to succeed");
+ }
+ }
+ return measurement;
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ DistanceMeasurement measurement = UwbTestUtils.getDistanceMeasurement();
+ measurement.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ DistanceMeasurement fromParcel =
+ DistanceMeasurement.CREATOR.createFromParcel(parcel);
+ assertEquals(measurement, fromParcel);
+ }
+}
diff --git a/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java b/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java
new file mode 100644
index 0000000..d57a636
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.os.SystemClock;
+import android.uwb.AngleOfArrivalMeasurement;
+import android.uwb.DistanceMeasurement;
+import android.uwb.RangingMeasurement;
+import android.uwb.UwbAddress;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link RangingMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingMeasurementTest {
+
+ @Test
+ public void testBuilder() {
+ int status = RangingMeasurement.RANGING_STATUS_SUCCESS;
+ UwbAddress address = UwbTestUtils.getUwbAddress(false);
+ long time = SystemClock.elapsedRealtimeNanos();
+ AngleOfArrivalMeasurement angleMeasurement = UwbTestUtils.getAngleOfArrivalMeasurement();
+ DistanceMeasurement distanceMeasurement = UwbTestUtils.getDistanceMeasurement();
+
+ RangingMeasurement.Builder builder = new RangingMeasurement.Builder();
+
+ builder.setStatus(status);
+ tryBuild(builder, false);
+
+ builder.setElapsedRealtimeNanos(time);
+ tryBuild(builder, false);
+
+ builder.setAngleOfArrivalMeasurement(angleMeasurement);
+ tryBuild(builder, false);
+
+ builder.setDistanceMeasurement(distanceMeasurement);
+ tryBuild(builder, false);
+
+ builder.setRemoteDeviceAddress(address);
+ RangingMeasurement measurement = tryBuild(builder, true);
+
+ assertEquals(status, measurement.getStatus());
+ assertEquals(address, measurement.getRemoteDeviceAddress());
+ assertEquals(time, measurement.getElapsedRealtimeNanos());
+ assertEquals(angleMeasurement, measurement.getAngleOfArrivalMeasurement());
+ assertEquals(distanceMeasurement, measurement.getDistanceMeasurement());
+ }
+
+ private RangingMeasurement tryBuild(RangingMeasurement.Builder builder,
+ boolean expectSuccess) {
+ RangingMeasurement measurement = null;
+ try {
+ measurement = builder.build();
+ if (!expectSuccess) {
+ fail("Expected RangingMeasurement.Builder.build() to fail");
+ }
+ } catch (IllegalStateException e) {
+ if (expectSuccess) {
+ fail("Expected DistanceMeasurement.Builder.build() to succeed");
+ }
+ }
+ return measurement;
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ RangingMeasurement measurement = UwbTestUtils.getRangingMeasurement();
+ measurement.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ RangingMeasurement fromParcel = RangingMeasurement.CREATOR.createFromParcel(parcel);
+ assertEquals(measurement, fromParcel);
+ }
+}
diff --git a/tests/uwb/src/android/uwb/cts/RangingReportTest.java b/tests/uwb/src/android/uwb/cts/RangingReportTest.java
new file mode 100644
index 0000000..b2524e7
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/RangingReportTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.uwb.RangingMeasurement;
+import android.uwb.RangingReport;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Test of {@link RangingReport}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingReportTest {
+
+ @Test
+ public void testBuilder() {
+ List<RangingMeasurement> measurements = UwbTestUtils.getRangingMeasurements(5);
+
+ RangingReport.Builder builder = new RangingReport.Builder();
+ builder.addMeasurements(measurements);
+ RangingReport report = tryBuild(builder, true);
+ verifyMeasurementsEqual(measurements, report.getMeasurements());
+
+
+ builder = new RangingReport.Builder();
+ for (RangingMeasurement measurement : measurements) {
+ builder.addMeasurement(measurement);
+ }
+ report = tryBuild(builder, true);
+ verifyMeasurementsEqual(measurements, report.getMeasurements());
+ }
+
+ private void verifyMeasurementsEqual(List<RangingMeasurement> expected,
+ List<RangingMeasurement> actual) {
+ assertEquals(expected.size(), actual.size());
+ for (int i = 0; i < expected.size(); i++) {
+ assertEquals(expected.get(i), actual.get(i));
+ }
+ }
+
+ private RangingReport tryBuild(RangingReport.Builder builder,
+ boolean expectSuccess) {
+ RangingReport report = null;
+ try {
+ report = builder.build();
+ if (!expectSuccess) {
+ fail("Expected RangingReport.Builder.build() to fail");
+ }
+ } catch (IllegalStateException e) {
+ if (expectSuccess) {
+ fail("Expected RangingReport.Builder.build() to succeed");
+ }
+ }
+ return report;
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ RangingReport report = UwbTestUtils.getRangingReports(5);
+ report.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ RangingReport fromParcel = RangingReport.CREATOR.createFromParcel(parcel);
+ assertEquals(report, fromParcel);
+ }
+}
diff --git a/tests/uwb/src/android/uwb/cts/RangingSessionTest.java b/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
new file mode 100644
index 0000000..b936142
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb.cts;
+
+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.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.uwb.IUwbAdapter;
+import android.uwb.RangingReport;
+import android.uwb.RangingSession;
+import android.uwb.SessionHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Test of {@link RangingSession}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingSessionTest {
+ private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
+ private static final PersistableBundle PARAMS = new PersistableBundle();
+ private static final @RangingSession.Callback.Reason int REASON =
+ RangingSession.Callback.REASON_GENERIC_ERROR;
+
+ @Test
+ public void testOnRangingOpened_OnOpenSuccessCalled() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.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());
+ }
+
+ @Test
+ public void testOnRangingOpened_CannotOpenClosedSession() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+
+ session.onRangingOpened();
+ verifyOpenState(session, true);
+ verify(callback, times(1)).onOpened(eq(session));
+ verify(callback, times(0)).onClosed(anyInt(), any());
+
+ session.onRangingClosed(REASON, PARAMS);
+ verifyOpenState(session, false);
+ verify(callback, times(1)).onOpened(eq(session));
+ verify(callback, times(1)).onClosed(anyInt(), any());
+
+ // Now invoke the ranging started callback and ensure the session remains closed
+ session.onRangingOpened();
+ verifyOpenState(session, false);
+ verify(callback, times(1)).onOpened(eq(session));
+ verify(callback, times(1)).onClosed(anyInt(), any());
+ }
+
+ @Test
+ public void testOnRangingClosed_OnClosedCalledWhenSessionNotOpen() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+ verifyOpenState(session, false);
+
+ session.onRangingClosed(REASON, PARAMS);
+ verifyOpenState(session, false);
+
+ // Verify that the onOpenSuccess callback was invoked
+ verify(callback, times(0)).onOpened(eq(session));
+ verify(callback, times(1)).onClosed(anyInt(), any());
+ }
+
+ @Test
+ public void testOnRangingClosed_OnClosedCalled() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+ session.onRangingStarted(PARAMS);
+ session.onRangingClosed(REASON, PARAMS);
+ verify(callback, times(1)).onClosed(anyInt(), any());
+
+ verifyOpenState(session, false);
+ session.onRangingClosed(REASON, PARAMS);
+ verify(callback, times(2)).onClosed(anyInt(), any());
+ }
+
+ @Test
+ public void testOnRangingResult_OnReportReceivedCalled() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+ verifyOpenState(session, false);
+
+ session.onRangingStarted(PARAMS);
+ verifyOpenState(session, true);
+
+ RangingReport report = UwbTestUtils.getRangingReports(1);
+ session.onRangingResult(report);
+ verify(callback, times(1)).onReportReceived(eq(report));
+ }
+
+ @Test
+ public void testStart_CannotStartIfAlreadyStarted() throws RemoteException {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+ doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
+ session.onRangingOpened();
+
+ session.start(PARAMS);
+ verify(callback, times(1)).onStarted(any());
+
+ // Calling start again should throw an illegal state
+ verifyThrowIllegalState(() -> session.start(PARAMS));
+ verify(callback, times(1)).onStarted(any());
+ }
+
+ @Test
+ public void testStop_CannotStopIfAlreadyStopped() throws RemoteException {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.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());
+ session.onRangingOpened();
+ session.start(PARAMS);
+
+ verifyNoThrowIllegalState(session::stop);
+ verify(callback, times(1)).onStopped();
+
+ // Calling stop again should throw an illegal state
+ verifyThrowIllegalState(session::stop);
+ verify(callback, times(1)).onStopped();
+ }
+
+ @Test
+ public void testReconfigure_OnlyWhenOpened() throws RemoteException {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+ doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
+ doAnswer(new ReconfigureAnswer(session)).when(adapter).reconfigureRanging(any(), any());
+
+ verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
+ verify(callback, times(0)).onReconfigured(any());
+ verifyOpenState(session, false);
+
+ session.onRangingOpened();
+ verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
+ verify(callback, times(1)).onReconfigured(any());
+ verifyOpenState(session, true);
+
+ session.onRangingStarted(PARAMS);
+ verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
+ verify(callback, times(2)).onReconfigured(any());
+ verifyOpenState(session, true);
+
+ session.onRangingStopped();
+ verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
+ verify(callback, times(3)).onReconfigured(any());
+ verifyOpenState(session, true);
+
+
+ session.onRangingClosed(REASON, PARAMS);
+ verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
+ verify(callback, times(3)).onReconfigured(any());
+ verifyOpenState(session, false);
+ }
+
+ @Test
+ public void testClose_NoCallbackUntilInvoked() throws RemoteException {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+ session.onRangingOpened();
+
+ // Calling close multiple times should invoke closeRanging until the session receives
+ // the onClosed callback.
+ int totalCallsBeforeOnRangingClosed = 3;
+ for (int i = 1; i <= totalCallsBeforeOnRangingClosed; i++) {
+ session.close();
+ verifyOpenState(session, true);
+ verify(adapter, times(i)).closeRanging(handle);
+ verify(callback, times(0)).onClosed(anyInt(), any());
+ }
+
+ // After onClosed is invoked, then the adapter should no longer be called for each call to
+ // the session's close.
+ final int totalCallsAfterOnRangingClosed = 2;
+ for (int i = 1; i <= totalCallsAfterOnRangingClosed; i++) {
+ session.onRangingClosed(REASON, PARAMS);
+ verifyOpenState(session, false);
+ verify(adapter, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle);
+ verify(callback, times(i)).onClosed(anyInt(), any());
+ }
+ }
+
+ @Test
+ public void testClose_OnClosedCalled() throws RemoteException {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+ doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
+ session.onRangingOpened();
+
+ session.close();
+ verify(callback, times(1)).onClosed(anyInt(), any());
+ }
+
+ @Test
+ public void testClose_CannotInteractFurther() throws RemoteException {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+ doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
+ session.close();
+
+ verifyThrowIllegalState(() -> session.start(PARAMS));
+ verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
+ verifyThrowIllegalState(() -> session.stop());
+ verifyNoThrowIllegalState(() -> session.close());
+ }
+
+ @Test
+ public void testOnRangingResult_OnReportReceivedCalledWhenOpen() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+
+ assertFalse(session.isOpen());
+ session.onRangingStarted(PARAMS);
+ assertTrue(session.isOpen());
+
+ // Verify that the onReportReceived callback was invoked
+ RangingReport report = UwbTestUtils.getRangingReports(1);
+ session.onRangingResult(report);
+ verify(callback, times(1)).onReportReceived(report);
+ }
+
+ @Test
+ public void testOnRangingResult_OnReportReceivedNotCalledWhenNotOpen() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ IUwbAdapter adapter = mock(IUwbAdapter.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+
+ assertFalse(session.isOpen());
+
+ // Verify that the onReportReceived callback was invoked
+ RangingReport report = UwbTestUtils.getRangingReports(1);
+ session.onRangingResult(report);
+ verify(callback, times(0)).onReportReceived(report);
+ }
+
+ private void verifyOpenState(RangingSession session, boolean expected) {
+ assertEquals(expected, session.isOpen());
+ }
+
+ private void verifyThrowIllegalState(Runnable runnable) {
+ try {
+ runnable.run();
+ fail();
+ } catch (IllegalStateException e) {
+ // Pass
+ }
+ }
+
+ private void verifyNoThrowIllegalState(Runnable runnable) {
+ try {
+ runnable.run();
+ } catch (IllegalStateException e) {
+ fail();
+ }
+ }
+
+ abstract class AdapterAnswer implements Answer {
+ protected RangingSession mSession;
+
+ protected AdapterAnswer(RangingSession session) {
+ mSession = session;
+ }
+ }
+
+ class StartAnswer extends AdapterAnswer {
+ StartAnswer(RangingSession session) {
+ super(session);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ mSession.onRangingStarted(PARAMS);
+ return null;
+ }
+ }
+
+ class ReconfigureAnswer extends AdapterAnswer {
+ ReconfigureAnswer(RangingSession session) {
+ super(session);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ mSession.onRangingReconfigured(PARAMS);
+ return null;
+ }
+ }
+
+ class StopAnswer extends AdapterAnswer {
+ StopAnswer(RangingSession session) {
+ super(session);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ mSession.onRangingStopped();
+ return null;
+ }
+ }
+
+ class CloseAnswer extends AdapterAnswer {
+ CloseAnswer(RangingSession session) {
+ super(session);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ mSession.onRangingClosed(REASON, PARAMS);
+ return null;
+ }
+ }
+}
diff --git a/tests/uwb/src/android/uwb/cts/SessionHandleTest.java b/tests/uwb/src/android/uwb/cts/SessionHandleTest.java
new file mode 100644
index 0000000..d52a3e7
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/SessionHandleTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.uwb.SessionHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link SessionHandle}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SessionHandleTest {
+
+ @Test
+ public void testBasic() {
+ int handleId = 12;
+ SessionHandle handle = new SessionHandle(handleId);
+ assertEquals(handle.getId(), handleId);
+ }
+
+ @Test
+ public void testParcel() {
+ Parcel parcel = Parcel.obtain();
+ SessionHandle handle = new SessionHandle(10);
+ handle.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ SessionHandle fromParcel = SessionHandle.CREATOR.createFromParcel(parcel);
+ assertEquals(handle, fromParcel);
+ }
+}
diff --git a/tests/uwb/src/android/uwb/cts/UwbAddressTest.java b/tests/uwb/src/android/uwb/cts/UwbAddressTest.java
new file mode 100644
index 0000000..d2f4228
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/UwbAddressTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.uwb.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.uwb.UwbAddress;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link UwbAddress}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UwbAddressTest {
+
+ @Test
+ public void testFromBytes_Short() {
+ runFromBytes(UwbAddress.SHORT_ADDRESS_BYTE_LENGTH);
+ }
+
+ @Test
+ public void testFromBytes_Extended() {
+ runFromBytes(UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH);
+ }
+
+ private void runFromBytes(int len) {
+ byte[] addressBytes = getByteArray(len);
+ UwbAddress address = UwbAddress.fromBytes(addressBytes);
+ assertEquals(address.size(), len);
+ assertEquals(addressBytes, address.toBytes());
+ }
+
+ private byte[] getByteArray(int len) {
+ byte[] res = new byte[len];
+ for (int i = 0; i < len; i++) {
+ res[i] = (byte) i;
+ }
+ return res;
+ }
+
+ @Test
+ public void testParcel_Short() {
+ runParcel(true);
+ }
+
+ @Test
+ public void testParcel_Extended() {
+ runParcel(false);
+ }
+
+ private void runParcel(boolean useShortAddress) {
+ Parcel parcel = Parcel.obtain();
+ UwbAddress address = UwbTestUtils.getUwbAddress(useShortAddress);
+ address.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ UwbAddress fromParcel = UwbAddress.CREATOR.createFromParcel(parcel);
+ assertEquals(address, fromParcel);
+ }
+}
diff --git a/tests/uwb/src/android/uwb/cts/UwbManagerTest.java b/tests/uwb/src/android/uwb/cts/UwbManagerTest.java
new file mode 100644
index 0000000..de1265e
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/UwbManagerTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.uwb.cts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.uwb.UwbManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link UwbManager}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UwbManagerTest {
+
+ public final Context mContext = InstrumentationRegistry.getContext();
+
+ @Test
+ public void testServiceAvailable() {
+ UwbManager manager = mContext.getSystemService(UwbManager.class);
+ if (UwbTestUtils.isUwbSupported(mContext)) {
+ assertNotNull(manager);
+ } else {
+ assertNull(manager);
+ }
+ }
+}
diff --git a/tests/uwb/src/android/uwb/cts/UwbTestUtils.java b/tests/uwb/src/android/uwb/cts/UwbTestUtils.java
new file mode 100644
index 0000000..b5d8e1b
--- /dev/null
+++ b/tests/uwb/src/android/uwb/cts/UwbTestUtils.java
@@ -0,0 +1,115 @@
+/*
+ * 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.uwb.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+import android.uwb.AngleMeasurement;
+import android.uwb.AngleOfArrivalMeasurement;
+import android.uwb.DistanceMeasurement;
+import android.uwb.RangingMeasurement;
+import android.uwb.RangingReport;
+import android.uwb.UwbAddress;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class UwbTestUtils {
+ private UwbTestUtils() {}
+
+ public static boolean isUwbSupported(Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_UWB);
+ }
+
+ public static AngleMeasurement getAngleMeasurement() {
+ return new AngleMeasurement.Builder()
+ .setRadians(getDoubleInRange(-Math.PI, Math.PI))
+ .setErrorRadians(getDoubleInRange(0, Math.PI))
+ .setConfidenceLevel(getDoubleInRange(0, 1))
+ .build();
+ }
+
+ public static AngleOfArrivalMeasurement getAngleOfArrivalMeasurement() {
+ return new AngleOfArrivalMeasurement.Builder()
+ .setAltitude(getAngleMeasurement())
+ .setAzimuth(getAngleMeasurement())
+ .build();
+ }
+
+ public static DistanceMeasurement getDistanceMeasurement() {
+ return new DistanceMeasurement.Builder()
+ .setMeters(getDoubleInRange(0, 100))
+ .setErrorMeters(getDoubleInRange(0, 10))
+ .setConfidenceLevel(getDoubleInRange(0, 1))
+ .build();
+ }
+
+ public static RangingMeasurement getRangingMeasurement() {
+ return getRangingMeasurement(getUwbAddress(false));
+ }
+
+ public static RangingMeasurement getRangingMeasurement(UwbAddress address) {
+ return new RangingMeasurement.Builder()
+ .setDistanceMeasurement(getDistanceMeasurement())
+ .setAngleOfArrivalMeasurement(getAngleOfArrivalMeasurement())
+ .setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos())
+ .setRemoteDeviceAddress(address != null ? address : getUwbAddress(false))
+ .setStatus(RangingMeasurement.RANGING_STATUS_SUCCESS)
+ .build();
+ }
+
+ public static List<RangingMeasurement> getRangingMeasurements(int num) {
+ List<RangingMeasurement> result = new ArrayList<>();
+ for (int i = 0; i < num; i++) {
+ result.add(getRangingMeasurement());
+ }
+ return result;
+ }
+
+ public static RangingReport getRangingReports(int numMeasurements) {
+ RangingReport.Builder builder = new RangingReport.Builder();
+ for (int i = 0; i < numMeasurements; i++) {
+ builder.addMeasurement(getRangingMeasurement());
+ }
+ return builder.build();
+ }
+
+ private static double getDoubleInRange(double min, double max) {
+ return min + (max - min) * Math.random();
+ }
+
+ public static UwbAddress getUwbAddress(boolean isShortAddress) {
+ byte[] addressBytes = new byte[isShortAddress ? UwbAddress.SHORT_ADDRESS_BYTE_LENGTH :
+ UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH];
+ for (int i = 0; i < addressBytes.length; i++) {
+ addressBytes[i] = (byte) getDoubleInRange(1, 255);
+ }
+ return UwbAddress.fromBytes(addressBytes);
+ }
+
+ public static Executor getExecutor() {
+ return new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ };
+ }
+}
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 8716562..0c73c32 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
@@ -537,6 +537,7 @@
charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS.getName());
charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS.getName());
charsKeyNames.add(CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES.getName());
+ charsKeyNames.add(CameraCharacteristics.SCALER_DEFAULT_SECURE_IMAGE_SIZE.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/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
index 7586074..096db55a 100644
--- a/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
+++ b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
@@ -108,6 +108,16 @@
"^CVE-\\d{4}-.+$"
};
+ private static final String[] BINARY_SUFFIX_EXCEPTIONS = {
+ /**
+ * All STS test binaries rvc+ are in the form of *_sts32 or *_sts64.
+ *
+ * Many STS binaries are only feasible on a specific bitness so STS
+ * pushes the appropriate binary to compatible devices.
+ */
+ "_sts32", "_sts64",
+ };
+
/**
* Test that all apks have the same supported abis.
* Sometimes, if a module is missing LOCAL_MULTILIB := both, we will end up with only one of
@@ -202,6 +212,11 @@
if (BINARY_EXCEPTIONS.contains(name)) {
return false;
}
+ for (String suffixException : BINARY_SUFFIX_EXCEPTIONS) {
+ if (name.endsWith(suffixException)) {
+ return false;
+ }
+ }
File file = new File(dir, name);
if (file.isDirectory()) {
return false;
diff --git a/tools/manifest-generator/tests/Android.bp b/tools/manifest-generator/tests/Android.bp
index b8f012d..2e34a91 100644
--- a/tools/manifest-generator/tests/Android.bp
+++ b/tools/manifest-generator/tests/Android.bp
@@ -17,8 +17,11 @@
srcs: ["src/**/*.java"],
- libs: [
+ static_libs: [
"compatibility-manifest-generator",
"junit",
],
+ test_options: {
+ unit_test: true,
+ },
}