diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-12-05 11:48:34 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-12-05 11:48:34 +0000 |
commit | e8af857ee857653c0b9c2f6cf8335811a4e727c3 (patch) | |
tree | 0639a3d5b9e5abbfeeb94a048db01bd328b2945e | |
parent | fb21a495a98459f724c2332213ba804e14c57b74 (diff) | |
parent | 05dcb2cb5181c8324e1233cd37efbc04d9035538 (diff) | |
download | cts-android14-mainline-art-release.tar.gz |
Snap for 11178562 from 05dcb2cb5181c8324e1233cd37efbc04d9035538 to mainline-art-releaseaml_art_341411300android14-mainline-art-release
Change-Id: I2a6b5a566e30ffbd2ea2bba342e47c312b725331
315 files changed, 7574 insertions, 2130 deletions
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py index b55168527a1..b6bf677d0ff 100644 --- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py +++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py @@ -388,7 +388,8 @@ class MultiCameraAlignmentTest(its_base_test.ItsBaseTest): if (media_performance_class >= _TEST_REQUIRED_MPC and not should_run and cam.is_primary_camera() and - has_multiple_same_facing_cameras): + has_multiple_same_facing_cameras and + props['android.lens.facing'] == _LENS_FACING_BACK): logging.error('Found multiple camera IDs %s facing in the same ' 'direction as primary camera %s.', cameras_facing_same_direction, self.camera_id) diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py index 0df3fd0d701..99815a9822a 100755 --- a/apps/CameraITS/tools/run_all_tests.py +++ b/apps/CameraITS/tools/run_all_tests.py @@ -29,6 +29,7 @@ import image_processing_utils import its_session_utils import numpy as np import yaml +import lighting_control_utils YAML_FILE_DIR = os.environ['CAMERA_ITS_TOP'] CONFIG_FILE = os.path.join(YAML_FILE_DIR, 'config.yml') @@ -986,6 +987,16 @@ def main(): cmd = f'adb -s {tablet_id} shell input keyevent KEYCODE_POWER' subprocess.Popen(cmd.split()) + # establish connection with lighting controller + lighting_cntl = test_params_content.get('lighting_cntl', 'None') + lighting_ch = test_params_content.get('lighting_ch', 'None') + arduino_serial_port = lighting_control_utils.lighting_control( + lighting_cntl, lighting_ch) + + # turn OFF lights + lighting_control_utils.set_lighting_state( + arduino_serial_port, lighting_ch, 'OFF') + if num_testbeds is not None: if testbed_index == _MAIN_TESTBED: logging.info('Waiting for all testbeds to finish.') diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml index 29e4577dad1..9fb158f3212 100644 --- a/apps/CtsVerifier/AndroidManifest.xml +++ b/apps/CtsVerifier/AndroidManifest.xml @@ -18,7 +18,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.cts.verifier" android:versionCode="5" - android:versionName="14_r2"> + android:versionName="14_r3"> <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="34"/> @@ -214,7 +214,9 @@ <meta-data android:name="display_mode" android:value="multi_display_mode" /> <meta-data android:name="CddTest" - android:value="2.2.4/8.3/H-1-1|2.3.4/8.3/T-1-1|2.4.4/8.3/W-SR|8.3/C-SR" /> + android:value="2.2.4/8.3/H-1-1|2.3.4/8.3/T-1-1| + 2.4.4/8.3/W-SR-1,8.3/W-SR-2| + 8.3/C-SR-1,C-SR-2" /> <meta-data android:name="ApiTest" android:value="android.os.PowerManager#isPowerSaveMode" /> </activity> @@ -4516,10 +4518,13 @@ android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> + <action android:name="android.intent.action.SEARCH" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> </intent-filter> + <meta-data android:name="android.app.searchable" + android:resource="@xml/searchable"/> </activity-alias> <!-- <activity-alias--> @@ -6838,6 +6843,7 @@ </intent-filter> <meta-data android:name="test_category" android:value="@string/test_category_input"/> <meta-data android:name="ApiTest" android:value="android.view.InputDevice#getUsiVersion" /> + <meta-data android:name="display_mode" android:value="single_display_mode" /> </activity> <!-- Components used for CTS Verifying Capture Content For Notes APIs. --> diff --git a/apps/CtsVerifier/res/menu/test_list_menu.xml b/apps/CtsVerifier/res/menu/test_list_menu.xml index 2c104a0a34b..95bfd5877e9 100644 --- a/apps/CtsVerifier/res/menu/test_list_menu.xml +++ b/apps/CtsVerifier/res/menu/test_list_menu.xml @@ -13,4 +13,10 @@ android:icon="@android:drawable/ic_menu_save" android:title="@string/export" android:showAsAction="ifRoom" /> + <item + android:id="@+id/search_test" + android:title="@string/search_title" + android:actionViewClass="android.widget.SearchView" + android:showAsAction="collapseActionView|ifRoom" + android:imeOptions="actionSearch" /> </menu> diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml index 4967f32879b..d7cd7856239 100644 --- a/apps/CtsVerifier/res/values/strings.xml +++ b/apps/CtsVerifier/res/values/strings.xml @@ -33,6 +33,9 @@ <string name="fail_and_next_button_text">Fail and Next</string> <string name="yes_string">Yes</string> <string name="no_string">No</string> + <string name="search_label">TestListActivity</string> + <string name="search_hint">Search Tests</string> + <string name="search_title">Search</string> <!-- Strings for CtsReportLog warning --> <string name="reportlog_warning_title">CTS-Verifier Report Log</string> @@ -4462,7 +4465,7 @@ You should be prompted to select credentials; choose the ones you just installed Set \'%s\' user restriction by turning on the switch below. </string> <string name="disallow_add_user">Disallow add user</string> - <string name="disallow_add_user_action">Accessing the multiple users settings page, or if the device allows access to the page, then attempting to add a new user </string> + <string name="disallow_add_user_action">Accessing the multiple users settings page, or if the device allows access to the page, then attempting to enable the feature </string> <string name="disallow_adjust_volume">Disallow adjust volume</string> <string name="disallow_adjust_volume_action">Adjusting the volume\n NOTE: If the device does not support volume adjustment in Settings app please skip this test and mark it as passing.\n</string> diff --git a/apps/CtsVerifier/res/xml/searchable.xml b/apps/CtsVerifier/res/xml/searchable.xml new file mode 100644 index 00000000000..a443c076ee3 --- /dev/null +++ b/apps/CtsVerifier/res/xml/searchable.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<searchable xmlns:android="http://schemas.android.com/apk/res/android" + android:label="@string/search_label" + android:hint="@string/search_hint" > +</searchable>
\ No newline at end of file diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java index b79840c4c1f..c37788d97a7 100644 --- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java +++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java @@ -17,7 +17,6 @@ package com.android.cts.verifier; import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode; -import static com.android.cts.verifier.TestListActivity.sInitialLaunch; import android.annotation.SuppressLint; import android.content.Context; @@ -47,84 +46,76 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; /** - * {@link TestListAdapter} that populates the {@link TestListActivity}'s {@link ListView} by - * reading data from the CTS Verifier's AndroidManifest.xml. - * <p> - * Making a new test activity to appear in the list requires the following steps: + * {@link TestListAdapter} that populates the {@link TestListActivity}'s {@link ListView} by reading + * data from the CTS Verifier's AndroidManifest.xml. + * + * <p>Making a new test activity to appear in the list requires the following steps: * * <ol> - * <li>REQUIRED: Add an activity to the AndroidManifest.xml with an intent filter with a - * main action and the MANUAL_TEST category. - * <pre> + * <li>REQUIRED: Add an activity to the AndroidManifest.xml with an intent filter with a main + * action and the MANUAL_TEST category. + * <pre> * <intent-filter> * <action android:name="android.intent.action.MAIN" /> * <category android:name="android.cts.intent.category.MANUAL_TEST" /> * </intent-filter> * </pre> - * </li> - * <li>REQUIRED: Add a meta data attribute to indicate which display modes of tests the activity - * should belong to. "single_display_mode" indicates a test is only needed to run on the - * main display mode (i.e. unfolded), and "multi_display_mode" indicates a test is required - * to run under both modes (i.e. both folded and unfolded).If you don't add this attribute, - * your test will show up in both unfolded and folded modes. - * <pre> + * <li>REQUIRED: Add a meta data attribute to indicate which display modes of tests the activity + * should belong to. "single_display_mode" indicates a test is only needed to run on the main + * display mode (i.e. unfolded), and "multi_display_mode" indicates a test is required to run + * under both modes (i.e. both folded and unfolded).If you don't add this attribute, your test + * will show up in both unfolded and folded modes. + * <pre> * <meta-data android:name="display_mode" android:value="multi_display_mode" /> * </pre> - * </li> - * <li>OPTIONAL: Add a meta data attribute to indicate what category of tests the activity - * should belong to. If you don't add this attribute, your test will show up in the - * "Other" tests category. - * <pre> + * <li>OPTIONAL: Add a meta data attribute to indicate what category of tests the activity should + * belong to. If you don't add this attribute, your test will show up in the "Other" tests + * category. + * <pre> * <meta-data android:name="test_category" android:value="@string/test_category_security" /> * </pre> - * </li> - * <li>OPTIONAL: Add a meta data attribute to indicate whether this test has a parent test. - * <pre> + * <li>OPTIONAL: Add a meta data attribute to indicate whether this test has a parent test. + * <pre> * <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" /> * </pre> - * </li> - * <li>OPTIONAL: Add a meta data attribute to indicate what features are required to run the - * test. If the device does not have all of the required features then it will not appear - * in the test list. Use a colon (:) to specify multiple required features. - * <pre> + * <li>OPTIONAL: Add a meta data attribute to indicate what features are required to run the test. + * If the device does not have all of the required features then it will not appear in the + * test list. Use a colon (:) to specify multiple required features. + * <pre> * <meta-data android:name="test_required_features" android:value="android.hardware.sensor.accelerometer" /> * </pre> - * </li> - * <li>OPTIONAL: Add a meta data attribute to indicate features such that, if any present, the - * test gets excluded from being shown. If the device has any of the excluded features then - * the test will not appear in the test list. Use a colon (:) to specify multiple features - * to exclude for the test. Note that the colon means "or" in this case. - * <pre> + * <li>OPTIONAL: Add a meta data attribute to indicate features such that, if any present, the + * test gets excluded from being shown. If the device has any of the excluded features then + * the test will not appear in the test list. Use a colon (:) to specify multiple features to + * exclude for the test. Note that the colon means "or" in this case. + * <pre> * <meta-data android:name="test_excluded_features" android:value="android.hardware.type.television" /> * </pre> - * </li> - * <li>OPTIONAL: Add a meta data attribute to indicate features such that, if any present, - * the test is applicable to run. If the device has any of the applicable features then - * the test will appear in the test list. Use a colon (:) to specify multiple features - * <pre> + * <li>OPTIONAL: Add a meta data attribute to indicate features such that, if any present, the + * test is applicable to run. If the device has any of the applicable features then the test + * will appear in the test list. Use a colon (:) to specify multiple features + * <pre> * <meta-data android:name="test_applicable_features" android:value="android.hardware.sensor.compass" /> * </pre> - * </li> - * <li>OPTIONAL: Add a meta data attribute to indicate which intent actions are required to run - * the test. If the device does not have activities that handle all those actions, then it - * will not appear in the test list. Use a colon (:) to specify multiple required intent actions. - * <pre> + * <li>OPTIONAL: Add a meta data attribute to indicate which intent actions are required to run + * the test. If the device does not have activities that handle all those actions, then it + * will not appear in the test list. Use a colon (:) to specify multiple required intent + * actions. + * <pre> * <meta-data android:name="test_required_actions" android:value="android.app.action.ADD_DEVICE_ADMIN" /> * </pre> - * </li> - * <li>OPTIONAL: Add a meta data attribute to indicate which intent actions should not run when - * the user running the test is of the given "type" (notice that the type here is not - * necessarily the same as {@link UserManager#getUserType()}). - * Use a colon (:) to specify multiple user types. - * <pre> + * <li>OPTIONAL: Add a meta data attribute to indicate which intent actions should not run when + * the user running the test is of the given "type" (notice that the type here is not + * necessarily the same as {@link UserManager#getUserType()}). Use a colon (:) to specify + * multiple user types. + * <pre> * <meta-data android:name="test_excluded_user_types" android:value="visible_background_non-profile_user" /> * </pre> - * </li> - * * </ol> */ public class ManifestTestListAdapter extends TestListAdapter { @@ -166,19 +157,25 @@ public class ManifestTestListAdapter extends TestListAdapter { private static final String CONFIG_HAS_CAMERA_TOGGLE = "config_has_camera_toggle"; - /** The config to represent that a test is only needed to run in the main display mode - * (i.e. unfolded) */ + /** + * The config to represent that a test is only needed to run in the main display mode (i.e. + * unfolded). + */ private static final String SINGLE_DISPLAY_MODE = "single_display_mode"; - /** The config to represent that a test is needed to run in the multiple display modes - * (i.e. both unfolded and folded) */ + /** + * The config to represent that a test is needed to run in the multiple display modes (i.e. both + * unfolded and folded). + */ private static final String MULTIPLE_DISPLAY_MODE = "multi_display_mode"; /** The config to represent that a test is only needed to run in the folded display mode. */ private static final String FOLDED_DISPLAY_MODE = "folded_display_mode"; - /** The config to represent that a test is marked as pass when it passes either in folded mode - * or in unfolded mode. */ + /** + * The config to represent that a test is marked as pass when it passes either in folded mode or + * in unfolded mode. + */ private static final String EITHER_MODE = "either_mode"; /** @@ -193,6 +190,10 @@ public class ManifestTestListAdapter extends TestListAdapter { private String mTestParent; + public ManifestTestListAdapter(Context context, String testParent) { + this(context, testParent, context.getResources().getStringArray(R.array.disabled_tests)); + } + public ManifestTestListAdapter(Context context, String testParent, String[] disabledTestArray) { super(context); mContext = context; @@ -203,42 +204,39 @@ public class ManifestTestListAdapter extends TestListAdapter { } } - public ManifestTestListAdapter(Context context, String testParent) { - this(context, testParent, context.getResources().getStringArray(R.array.disabled_tests)); - } - @Override protected List<TestListItem> getRows() { - List<TestListItem> allRows = new ArrayList<TestListItem>(); - // When launching at the first time or after killing the process, needs to fetch the // test items of all display modes as the bases for switching. if (mDisplayModesTests.isEmpty()) { for (DisplayMode mode : DisplayMode.values()) { - allRows = getRowsWithDisplayMode(mode.toString()); - mDisplayModesTests.put(mode.toString(), allRows); + mDisplayModesTests.put(mode.toString(), getRowsWithDisplayMode(mode.toString())); PackageManager packageManager = mContext.getPackageManager(); - boolean isCustomLauncher = packageManager.hasSystemFeature("com.google.android.tv.custom_launcher"); + boolean isCustomLauncher = + packageManager.hasSystemFeature("com.google.android.tv.custom_launcher"); if (isCustomLauncher) { mDisabledTests.add( - "com.android.cts.verifier.net.ConnectivityBackgroundTestActivity"); + "com.android.cts.verifier.net.ConnectivityBackgroundTestActivity"); } } } - if (!sInitialLaunch) { - return getRowsWithDisplayMode(sCurrentDisplayMode); + if (mTestFilter != null) { + // Filter test rows dynamically when the filter is specified. + return getRowsWithDisplayMode(sCurrentDisplayMode.toString()); + } else { + return mDisplayModesTests.getOrDefault( + sCurrentDisplayMode.toString(), new ArrayList<>()); } - return allRows; } /** * Gets all rows based on the specific display mode. * - * @param mode Given display mode. - * @return A list containing all test itmes in the given display mode. + * @param mode the given display mode + * @return a list containing all test itmes in the given display mode */ - private List<TestListItem> getRowsWithDisplayMode (String mode) { + private List<TestListItem> getRowsWithDisplayMode(String mode) { /* * 1. Get all the tests belonging to the test parent. * 2. Get all the tests keyed by their category. @@ -267,8 +265,9 @@ public class ManifestTestListAdapter extends TestListAdapter { mainIntent.setPackage(mContext.getPackageName()); PackageManager packageManager = mContext.getPackageManager(); - List<ResolveInfo> list = packageManager.queryIntentActivities(mainIntent, - PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); + List<ResolveInfo> list = + packageManager.queryIntentActivities( + mainIntent, PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); int size = list.size(); List<ResolveInfo> matchingList = new ArrayList<>(); @@ -305,9 +304,19 @@ public class ManifestTestListAdapter extends TestListAdapter { String displayMode = getDisplayMode(info.activityInfo.metaData); boolean passInEitherMode = getTestPassMode(info.activityInfo.metaData, displayMode); - TestListItem item = TestListItem.newTest(title, testName, intent, requiredFeatures, - requiredConfigs, requiredActions, excludedFeatures, applicableFeatures, - excludedUserTypes, displayMode, passInEitherMode); + TestListItem item = + TestListItem.newTest( + title, + testName, + intent, + requiredFeatures, + requiredConfigs, + requiredActions, + excludedFeatures, + applicableFeatures, + excludedUserTypes, + displayMode, + passInEitherMode); String testCategory = getTestCategory(mContext, info.activityInfo.metaData); addTestToCategory(testsByCategory, testCategory, item); @@ -372,8 +381,8 @@ public class ManifestTestListAdapter extends TestListAdapter { /** * Gets the configuration of the display mode per test. The default value is multi_display_mode. * - * @param metaData Given metadata of the display mode. - * @return A string representing the display mode of the test. + * @param metaData the given metadata of the display mode + * @return a string representing the display mode of the test */ static String getDisplayMode(Bundle metaData) { if (metaData == null) { @@ -386,9 +395,9 @@ public class ManifestTestListAdapter extends TestListAdapter { /** * Gets the configuration of the test pass mode per test. * - * @param metaData Given metadata of the test pass mode. - * @return A boolean representing whether the test can be marked as pass when it passes either - * in the folded mode or in the unfolded mode. + * @param metaData the given metadata of the test pass mode + * @return a boolean representing whether the test can be marked as pass when it passes either + * in the folded mode or in the unfolded mode */ static boolean getTestPassMode(Bundle metaData, String displayMode) { if (metaData == null || !displayMode.equals(MULTIPLE_DISPLAY_MODE)) { @@ -412,8 +421,10 @@ public class ManifestTestListAdapter extends TestListAdapter { return intent; } - static void addTestToCategory(Map<String, List<TestListItem>> testsByCategory, - String testCategory, TestListItem item) { + static void addTestToCategory( + Map<String, List<TestListItem>> testsByCategory, + String testCategory, + TestListItem item) { List<TestListItem> tests; if (testsByCategory.containsKey(testCategory)) { tests = testsByCategory.get(testCategory); @@ -470,11 +481,12 @@ public class ManifestTestListAdapter extends TestListAdapter { switch (config) { case CONFIG_NO_EMULATOR: try { - Method getStringMethod = ClassLoader.getSystemClassLoader() - .loadClass("android.os.SystemProperties") - .getMethod("get", String.class); - String emulatorKernel = (String) getStringMethod.invoke("0", - "ro.boot.qemu"); + Method getStringMethod = + ClassLoader.getSystemClassLoader() + .loadClass("android.os.SystemProperties") + .getMethod("get", String.class); + String emulatorKernel = + (String) getStringMethod.invoke("0", "ro.boot.qemu"); if (emulatorKernel.equals("1")) { return false; } @@ -483,8 +495,8 @@ public class ManifestTestListAdapter extends TestListAdapter { } break; case CONFIG_VOICE_CAPABLE: - TelephonyManager telephonyManager = context.getSystemService( - TelephonyManager.class); + TelephonyManager telephonyManager = + context.getSystemService(TelephonyManager.class); if (!telephonyManager.isVoiceCapable()) { return false; } @@ -518,9 +530,11 @@ public class ManifestTestListAdapter extends TestListAdapter { } break; case CONFIG_HAS_MIC_TOGGLE: - return isHardwareToggleSupported(context, SensorPrivacyManager.Sensors.MICROPHONE); + return isHardwareToggleSupported( + context, SensorPrivacyManager.Sensors.MICROPHONE); case CONFIG_HAS_CAMERA_TOGGLE: - return isHardwareToggleSupported(context, SensorPrivacyManager.Sensors.CAMERA); + return isHardwareToggleSupported( + context, SensorPrivacyManager.Sensors.CAMERA); default: break; } @@ -530,11 +544,11 @@ public class ManifestTestListAdapter extends TestListAdapter { } /** - * Check if the test should be ran by the given display mode. + * Checks if the test should be ran by the given display mode. * - * @param mode Configs of the display mode. - * @param currentMode Given display mode. - * @return True if the given display mode matches the configs, otherwise, return false; + * @param mode the display mode config of the test + * @param currentMode the given display mode + * @return true if the given display mode matches the configs, otherwise, return false */ private boolean matchDisplayMode(String mode, String currentMode) { if (mode == null) { @@ -552,9 +566,7 @@ public class ManifestTestListAdapter extends TestListAdapter { } } - /** - * Checks whether the test is being run by a user type that doesn't support it. - */ + /** Checks whether the test is being run by a user type that doesn't support it. */ private boolean matchAnyExcludedUserType(String[] userTypes) { if (userTypes == null) { return false; @@ -569,14 +581,28 @@ public class ManifestTestListAdapter extends TestListAdapter { } return false; default: - throw new IllegalArgumentException("Invalid " - + TEST_EXCLUDED_USER_TYPES_META_DATA + " value: " + userType); + throw new IllegalArgumentException( + "Invalid " + + TEST_EXCLUDED_USER_TYPES_META_DATA + + " value: " + + userType); } } return false; } + /** Checks whether the title of the test matches the test filter. */ + private boolean macthTestFilter(String testTitle) { + if (mTestFilter == null) { + return true; + } + return testTitle != null + && testTitle + .toLowerCase(Locale.getDefault()) + .contains(mTestFilter.toLowerCase(Locale.getDefault())); + } + private boolean isVisibleBackgroundNonProfileUser() { if (!SdkLevel.isAtLeastU()) { Log.d(LOG_TAG, "isVisibleBagroundNonProfileUser() returning false on pre-UDC device"); @@ -598,7 +624,9 @@ public class ManifestTestListAdapter extends TestListAdapter { } private static List<Integer> getHdmiDeviceType() - throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, + throws InvocationTargetException, + IllegalAccessException, + ClassNotFoundException, NoSuchMethodException { Method getStringMethod = ClassLoader.getSystemClassLoader() @@ -614,19 +642,21 @@ public class ManifestTestListAdapter extends TestListAdapter { } private static boolean hasBattery(Context context) { - final Intent batteryInfo = context.registerReceiver( - null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + final Intent batteryInfo = + context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); return batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); } List<TestListItem> filterTests(List<TestListItem> tests, String mode) { List<TestListItem> filteredTests = new ArrayList<>(); for (TestListItem test : tests) { - if (!hasAnyFeature(test.excludedFeatures) && hasAllFeatures(test.requiredFeatures) + if (!hasAnyFeature(test.excludedFeatures) + && hasAllFeatures(test.requiredFeatures) && hasAllActions(test.requiredActions) && matchAllConfigs(mContext, test.requiredConfigs) && matchDisplayMode(test.displayMode, mode) - && !matchAnyExcludedUserType(test.excludedUserTypes)) { + && !matchAnyExcludedUserType(test.excludedUserTypes) + && macthTestFilter(test.title)) { if (test.applicableFeatures == null || hasAnyFeature(test.applicableFeatures)) { // Add suffix in test name if the test is in the folded mode. test.testName = setTestNameSuffix(mode, test.testName); @@ -641,39 +671,15 @@ public class ManifestTestListAdapter extends TestListAdapter { return filteredTests; } - @Override - public int getCount() { - if (!sInitialLaunch && mTestParent == null) { - return mDisplayModesTests.getOrDefault(sCurrentDisplayMode, new ArrayList<>()).size(); - } - return super.getCount(); - } - - @Override - public TestListItem getItem(int position) { - if (mTestParent == null) { - return mDisplayModesTests.get(sCurrentDisplayMode).get(position); - } - return super.getItem(position); - } - - @Override - public void loadTestResults() { - if (mTestParent == null) { - new RefreshTestResultsTask(true).execute(); - } else { - super.loadTestResults(); - } - } - @SuppressLint("NewApi") private static boolean isHardwareToggleSupported(Context context, final int sensorType) { boolean isToggleSupported = false; - SensorPrivacyManager sensorPrivacyManager = context.getSystemService( - SensorPrivacyManager.class); + SensorPrivacyManager sensorPrivacyManager = + context.getSystemService(SensorPrivacyManager.class); if (sensorPrivacyManager != null) { - isToggleSupported = sensorPrivacyManager.supportsSensorToggle( - SensorPrivacyManager.TOGGLE_TYPE_HARDWARE, sensorType); + isToggleSupported = + sensorPrivacyManager.supportsSensorToggle( + SensorPrivacyManager.TOGGLE_TYPE_HARDWARE, sensorType); } return isToggleSupported; } diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java index 38fba3f138c..790bf73e956 100644 --- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java +++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java @@ -37,6 +37,7 @@ import android.view.MenuItem; import android.view.View; import android.view.Window; import android.widget.CompoundButton; +import android.widget.SearchView; import android.widget.Switch; import android.widget.Toast; @@ -53,8 +54,6 @@ public class TestListActivity extends AbstractTestListActivity implements View.O // Records the current display mode. // Default is unfolded mode, and it will be changed when clicking the switch button. public static volatile String sCurrentDisplayMode = DisplayMode.UNFOLDED.toString(); - // Flag of launch app to fetch the unfolded/folded tests in main view from AndroidManifest.xml. - protected static boolean sInitialLaunch; private String[] mRequestedPermissions; @@ -69,10 +68,10 @@ public class TestListActivity extends AbstractTestListActivity implements View.O } /** - * Coverts the mode as suffix with brackets for test name. + * Converts the mode as suffix with brackets for test name. * - * @return A string containing mode with brackets for folded mode; empty string for unfolded - * mode. + * @return a string containing mode with brackets for folded mode; empty string for unfolded + * mode */ public String asSuffix() { if (name().equals(FOLDED.name())) { @@ -129,7 +128,6 @@ public class TestListActivity extends AbstractTestListActivity implements View.O if (!isTaskRoot()) { finish(); } - sInitialLaunch = true; // Restores the last display mode when launching the app after killing the process. if (getCurrentDisplayMode().equals(DisplayMode.FOLDED.toString())) { @@ -147,7 +145,8 @@ public class TestListActivity extends AbstractTestListActivity implements View.O getListView().addFooterView(footer); } - setTestListAdapter(new ManifestTestListAdapter(this, null)); + setTestListAdapter( + new ManifestTestListAdapter(/* context= */ this, /* testParent= */ null)); } @Override @@ -221,6 +220,28 @@ public class TestListActivity extends AbstractTestListActivity implements View.O handleSwitchItemSelected(); } }); + + SearchView searchView = (SearchView) menu.findItem(R.id.search_test).getActionView(); + searchView.setOnQueryTextListener( + new SearchView.OnQueryTextListener() { + + public boolean onQueryTextSubmit(String query) { + Log.i(TAG, "Got submitted query: " + query); + handleQueryUpdated(query); + return true; + } + + public boolean onQueryTextChange(String newText) { + if (newText == null || newText.isEmpty()) { + Log.i(TAG, "Clear filter"); + handleQueryUpdated(newText); + return true; + } else { + return false; + } + } + }); + return true; } @@ -252,7 +273,7 @@ public class TestListActivity extends AbstractTestListActivity implements View.O new ReportExporter(this, mAdapter).execute(); } - // Sets up the flags after switching display mode and reloads tests on UI. + /** Sets up the flags after switching display mode and reloads tests on UI. */ private void handleSwitchItemSelected() { setCurrentDisplayMode(sCurrentDisplayMode); mAdapter.loadTestResults(); @@ -270,10 +291,21 @@ public class TestListActivity extends AbstractTestListActivity implements View.O return true; } + /** Triggered when a new query is input. */ + private void handleQueryUpdated(String query) { + if (query != null && !query.isEmpty()) { + mAdapter.setTestFilter(query); + } else { + // Reset the filter as null to show all tests. + mAdapter.setTestFilter(/* testFilter= */ null); + } + mAdapter.loadTestResults(); + } + /** * Sets current display mode to sharedpreferences. * - * @param mode A string of current display mode. + * @param mode a string of current display mode */ private void setCurrentDisplayMode(String mode) { SharedPreferences pref = getSharedPreferences(DisplayMode.class.getName(), MODE_PRIVATE); @@ -283,7 +315,7 @@ public class TestListActivity extends AbstractTestListActivity implements View.O /** * Gets current display mode from sharedpreferences. * - * @return A string of current display mode. + * @return a string of current display mode */ private String getCurrentDisplayMode() { String mode = diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java index d4007f9edea..870736f7b1a 100644 --- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java +++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java @@ -18,7 +18,6 @@ package com.android.cts.verifier; import static com.android.cts.verifier.ReportExporter.LOGS_DIRECTORY; import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode; -import static com.android.cts.verifier.TestListActivity.sInitialLaunch; import android.content.ContentResolver; import android.content.Context; @@ -103,6 +102,9 @@ public abstract class TestListAdapter extends BaseAdapter { */ protected Map<String, List<TestListItem>> mDisplayModesTests = new HashMap<>(); + /** A keyword to help filter out test cases by the test name. */ + protected String mTestFilter; + /** {@link ListView} row that is either a test category header or a test. */ public static class TestListItem { @@ -399,10 +401,7 @@ public abstract class TestListAdapter extends BaseAdapter { String displayMode, boolean passInEitherMode) { this.title = title; - if (!sInitialLaunch) { - testName = setTestNameSuffix(sCurrentDisplayMode, testName); - } - this.testName = testName; + this.testName = setTestNameSuffix(sCurrentDisplayMode, testName); this.intent = intent; this.requiredActions = requiredActions; this.requiredFeatures = requiredFeatures; @@ -431,7 +430,7 @@ public abstract class TestListAdapter extends BaseAdapter { } public void loadTestResults() { - new RefreshTestResultsTask(false).execute(); + new RefreshTestResultsTask().execute(); } public void clearTestResults() { @@ -446,37 +445,24 @@ public abstract class TestListAdapter extends BaseAdapter { histories.merge(null, mHistories.get(name)); new SetTestResultTask( - name, - testResult.getResult(), - testResult.getDetails(), - testResult.getReportLog(), - histories, - mScreenshotsMetadata.get(name)) + name, + testResult.getResult(), + testResult.getDetails(), + testResult.getReportLog(), + histories, + mScreenshotsMetadata.get(name)) .execute(); } - class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> { - - private boolean mIsFromMainView; + void setTestFilter(String testFilter) { + mTestFilter = testFilter; + } - RefreshTestResultsTask(boolean isFromMainView) { - mIsFromMainView = isFromMainView; - } + class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> { @Override protected RefreshResult doInBackground(Void... params) { - List<TestListItem> rows = getRows(); - // When initial launch, needs to fetch tests in the unfolded/folded mode - // to be stored in mDisplayModesTests as the basis for the future switch. - if (sInitialLaunch) { - sInitialLaunch = false; - } - - if (mIsFromMainView) { - rows = mDisplayModesTests.get(sCurrentDisplayMode); - } - - return getRefreshResults(rows); + return getRefreshResults(getRows()); } @Override @@ -526,13 +512,13 @@ public abstract class TestListAdapter extends BaseAdapter { protected abstract List<TestListItem> getRows(); static final String[] REFRESH_PROJECTION = { - TestResultsProvider._ID, - TestResultsProvider.COLUMN_TEST_NAME, - TestResultsProvider.COLUMN_TEST_RESULT, - TestResultsProvider.COLUMN_TEST_DETAILS, - TestResultsProvider.COLUMN_TEST_METRICS, - TestResultsProvider.COLUMN_TEST_RESULT_HISTORY, - TestResultsProvider.COLUMN_TEST_SCREENSHOTS_METADATA, + TestResultsProvider._ID, + TestResultsProvider.COLUMN_TEST_NAME, + TestResultsProvider.COLUMN_TEST_RESULT, + TestResultsProvider.COLUMN_TEST_DETAILS, + TestResultsProvider.COLUMN_TEST_METRICS, + TestResultsProvider.COLUMN_TEST_RESULT_HISTORY, + TestResultsProvider.COLUMN_TEST_SCREENSHOTS_METADATA, }; RefreshResult getRefreshResults(List<TestListItem> items) { @@ -639,12 +625,12 @@ public abstract class TestListAdapter extends BaseAdapter { ContentResolver resolver = mContext.getContentResolver(); try (Cursor cursor = - resolver.query( - TestResultsProvider.getTestNameUri(mContext, mTestName), - new String[] {TestResultsProvider.COLUMN_TEST_RESULT_HISTORY}, - null, - null, - null)) { + resolver.query( + TestResultsProvider.getTestNameUri(mContext, mTestName), + new String[] {TestResultsProvider.COLUMN_TEST_RESULT_HISTORY}, + null, + null, + null)) { if (cursor.moveToFirst()) { do { TestResultHistoryCollection historyCollection = @@ -723,7 +709,7 @@ public abstract class TestListAdapter extends BaseAdapter { /** Gets {@link TestListItem} with the given test name. */ public TestListItem getItemByName(String testName) { - for (TestListItem item: mRows) { + for (TestListItem item : mRows) { if (item != null && item.testName != null && item.testName.equals(testName)) { return item; } @@ -850,10 +836,11 @@ public abstract class TestListAdapter extends BaseAdapter { public boolean allTestsPassed() { for (TestListItem item : mRows) { - if (item != null && item.isTest() + if (item != null + && item.isTest() && (!mTestResults.containsKey(item.testName) - || (mTestResults.get(item.testName) - != TestResult.TEST_RESULT_PASSED))) { + || (mTestResults.get(item.testName) + != TestResult.TEST_RESULT_PASSED))) { return false; } } @@ -923,10 +910,13 @@ public abstract class TestListAdapter extends BaseAdapter { if (deviceStateManager == null) { return false; } - Set<Integer> supportedStates = Arrays.stream( - deviceStateManager.getSupportedStates()).boxed().collect(Collectors.toSet()); - int identifier = mContext.getResources().getIdentifier( - "config_foldedDeviceStates", "array", "android"); + Set<Integer> supportedStates = + Arrays.stream(deviceStateManager.getSupportedStates()) + .boxed() + .collect(Collectors.toSet()); + int identifier = + mContext.getResources() + .getIdentifier("config_foldedDeviceStates", "array", "android"); int[] foldedDeviceStates = mContext.getResources().getIntArray(identifier); return Arrays.stream(foldedDeviceStates).anyMatch(supportedStates::contains); } diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java index 0d0d423baa7..c5aeffb7846 100644 --- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java +++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java @@ -231,6 +231,7 @@ public class ItsService extends Service implements SensorEventListener { private SparseArray<String> mPhysicalStreamMap = new SparseArray<String>(); private SparseArray<Long> mStreamUseCaseMap = new SparseArray<Long>(); private ImageReader mInputImageReader = null; + private ImageReader mExtensionPreviewImageReader = null; private CameraCharacteristics mCameraCharacteristics = null; private CameraExtensionCharacteristics mCameraExtensionCharacteristics = null; private HashMap<String, CameraCharacteristics> mPhysicalCameraChars = @@ -339,6 +340,7 @@ public class ItsService extends Service implements SensorEventListener { public int videoFrameRate; // -1 implies video framerate was not set by the test public int fileFormat; public double zoomRatio; + public Map<String, String> metadata = new HashMap<>(); public VideoRecordingObject(String recordedOutputPath, String quality, Size videoSize, int videoFrameRate, @@ -360,6 +362,10 @@ public class ItsService extends Service implements SensorEventListener { public boolean isFrameRateValid() { return videoFrameRate != INVALID_FRAME_RATE; } + + public void addMetadata(String key, String value) { + metadata.put(key, value); + } } // For capturing motion sensor traces. @@ -1122,6 +1128,11 @@ public class ItsService extends Service implements SensorEventListener { videoJson.put("videoFrameRate", obj.videoFrameRate); } videoJson.put("videoSize", obj.videoSize); + JSONObject metadata = new JSONObject(); + for (Map.Entry<String, String> entry : obj.metadata.entrySet()) { + metadata.put(entry.getKey(), entry.getValue()); + } + videoJson.put("metadata", metadata); sendResponse("recordingResponse", null, videoJson, null); } catch (org.json.JSONException e) { throw new ItsException("JSON error: ", e); @@ -2363,6 +2374,7 @@ public class ItsService extends Service implements SensorEventListener { int recordingDuration, int videoStabilizationMode, boolean hlg10Enabled, double zoomRatio, int aeTargetFpsMin, int aeTargetFpsMax) throws ItsException { + ScalerCropRegionListener scalerCropRegionListener = new ScalerCropRegionListener(); final long SESSION_CLOSE_TIMEOUT_MS = 3000; if (!hlg10Enabled) { @@ -2436,7 +2448,7 @@ public class ItsService extends Service implements SensorEventListener { try { configureAndCreateCaptureSession(CameraDevice.TEMPLATE_RECORD, mRecordSurface, videoStabilizationMode, DynamicRangeProfiles.HLG10, mockCallback, - zoomRatio, aeTargetFpsMin, aeTargetFpsMax); + zoomRatio, aeTargetFpsMin, aeTargetFpsMax, scalerCropRegionListener); } catch (CameraAccessException e) { throw new ItsException("Access error: ", e); } @@ -2481,6 +2493,7 @@ public class ItsService extends Service implements SensorEventListener { private void doBasicRecording(String cameraId, int profileId, String quality, int recordingDuration, int videoStabilizationMode, double zoomRatio, int aeTargetFpsMin, int aeTargetFpsMax) throws ItsException { + ScalerCropRegionListener scalerCropRegionListener = new ScalerCropRegionListener(); int cameraDeviceId = Integer.parseInt(cameraId); mMediaRecorder = new MediaRecorder(); CamcorderProfile camcorderProfile = getCamcorderProfile(cameraDeviceId, profileId); @@ -2513,7 +2526,8 @@ public class ItsService extends Service implements SensorEventListener { try { configureAndCreateCaptureSession(CameraDevice.TEMPLATE_RECORD, mRecordSurface, videoStabilizationMode, DynamicRangeProfiles.STANDARD, - /*callback =*/ null, zoomRatio, aeTargetFpsMin, aeTargetFpsMax); + /*callback =*/ null, zoomRatio, aeTargetFpsMin, aeTargetFpsMax, + scalerCropRegionListener); } catch (android.hardware.camera2.CameraAccessException e) { throw new ItsException("Access error: ", e); } @@ -2561,7 +2575,7 @@ public class ItsService extends Service implements SensorEventListener { int recordingDuration, boolean stabilize, double zoomRatio, int aeTargetFpsMin, int aeTargetFpsMax) throws ItsException { - + ScalerCropRegionListener scalerCropRegionListener = new ScalerCropRegionListener(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { throw new ItsException("Cannot record preview before API level 33"); } @@ -2591,7 +2605,8 @@ public class ItsService extends Service implements SensorEventListener { : CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF; configureAndCreateCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pr.getCameraSurface(), stabilizationMode, DynamicRangeProfiles.STANDARD, - /*callback=*/ null, zoomRatio, aeTargetFpsMin, aeTargetFpsMax); + /*stateCallback=*/ null, zoomRatio, aeTargetFpsMin, aeTargetFpsMax, + scalerCropRegionListener); pr.recordPreview(recordingDuration * 1000L); mSession.close(); } catch (CameraAccessException e) { @@ -2602,6 +2617,8 @@ public class ItsService extends Service implements SensorEventListener { // Send VideoRecordingObject for further processing. VideoRecordingObject obj = new VideoRecordingObject(outputFilePath, /* quality= */"preview", videoSize, fileFormat, zoomRatio); + obj.addMetadata("SCALER_CROP_REGION", + scalerCropRegionListener.mScalerCropRegion.flattenToString()); mSocketRunnableObj.sendVideoRecordingObject(obj); } @@ -2633,16 +2650,21 @@ public class ItsService extends Service implements SensorEventListener { private Surface configureAndCreateExtensionSession( Surface captureSurface, int extension, - CameraExtensionSession.StateCallback stateCallback) throws ItsException { + CameraExtensionSession.StateCallback stateCallback, + boolean has10bitOutput) throws ItsException { int captureWidth = mOutputImageReaders[0].getWidth(); int captureHeight = mOutputImageReaders[0].getHeight(); Size captureSize = new Size(captureWidth, captureHeight); Log.i(TAG, "Capture size: " + captureSize.toString()); ArrayList outputConfig = new ArrayList<>(); - SurfaceTexture preview = new SurfaceTexture(/*random int*/ 1); Size previewSize = pickPreviewResolution(captureSize, extension); - preview.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); - Surface previewSurface = new Surface(preview); + mExtensionPreviewImageReader = ImageReader.newInstance( + previewSize.getWidth(), + previewSize.getHeight(), + ImageFormat.PRIVATE, + MAX_CONCURRENT_READER_BUFFERS, + HardwareBuffer.USAGE_CPU_READ_OFTEN | HardwareBuffer.USAGE_COMPOSER_OVERLAY); + Surface previewSurface = mExtensionPreviewImageReader.getSurface(); outputConfig.add(new OutputConfiguration(captureSurface)); outputConfig.add(new OutputConfiguration(previewSurface)); ExtensionSessionConfiguration extSessionConfig = new ExtensionSessionConfiguration( @@ -2661,7 +2683,8 @@ public class ItsService extends Service implements SensorEventListener { private void configureAndCreateCaptureSession(int requestTemplate, Surface recordSurface, int videoStabilizationMode, long dynamicRangeProfile, CameraCaptureSession.StateCallback stateCallback, - double zoomRatio, int aeTargetFpsMin, int aeTargetFpsMax) throws CameraAccessException { + double zoomRatio, int aeTargetFpsMin, int aeTargetFpsMax, + CameraCaptureSession.CaptureCallback captureCallback) throws CameraAccessException { assert (recordSurface != null); // Create capture request builder mCaptureRequestBuilder = mCamera.createCaptureRequest(requestTemplate); @@ -2713,7 +2736,8 @@ public class ItsService extends Service implements SensorEventListener { public void onConfigured(CameraCaptureSession session) { mSession = session; try { - mSession.setRepeatingRequest(mCaptureRequestBuilder.build(), null, null); + mSession.setRepeatingRequest(mCaptureRequestBuilder.build(), + captureCallback, mResultHandler); } catch (CameraAccessException e) { e.printStackTrace(); } @@ -2924,28 +2948,33 @@ public class ItsService extends Service implements SensorEventListener { JSONArray jsonOutputSpecs = ItsUtils.getOutputSpecs(params); - prepareImageReadersWithOutputSpecs(jsonOutputSpecs, /*inputSize*/null, - /*inputFormat*/0, /*maxInputBuffers*/0, /*backgroundRequest*/ false); + boolean has10bitOutput = prepareImageReadersWithOutputSpecs(jsonOutputSpecs, + /*inputSize*/null, /*inputFormat*/0, /*maxInputBuffers*/0, + /*backgroundRequest*/ false); numSurfaces = mOutputImageReaders.length; numCaptureSurfaces = numSurfaces; Surface previewSurface = configureAndCreateExtensionSession( mOutputImageReaders[0].getSurface(), extension, - sessionListener); + sessionListener, + has10bitOutput); mExtensionSession = sessionListener.waitAndGetSession(TIMEOUT_IDLE_MS); CaptureRequest.Builder captureBuilder = requests.get(0); if (params.optBoolean("waitAE", true)) { - // Set repeating request and wait for AE convergence. + if (mExtensionPreviewImageReader == null) { + throw new ItsException("Preview ImageReader has not been initialized!"); + } + // Set repeating request and wait for AE convergence, using another ImageReader. Logt.i(TAG, "Waiting for AE to converge before taking extensions capture."); - captureBuilder.addTarget(previewSurface); + captureBuilder.addTarget(mExtensionPreviewImageReader.getSurface()); ImageReader.OnImageAvailableListener dropperListener = createAvailableListenerDropper(); - mOutputImageReaders[0].setOnImageAvailableListener(dropperListener, - mSaveHandlers[0]); + mExtensionPreviewImageReader.setOnImageAvailableListener(dropperListener, + mSaveHandlers[0]); mExtensionSession.setRepeatingRequest(captureBuilder.build(), new HandlerExecutor(mResultHandler), mExtAEResultListener); @@ -2953,7 +2982,7 @@ public class ItsService extends Service implements SensorEventListener { long timeout = TIMEOUT_CALLBACK * 1000; waitForCallbacks(timeout); mExtensionSession.stopRepeating(); - captureBuilder.removeTarget(previewSurface); + captureBuilder.removeTarget(mExtensionPreviewImageReader.getSurface()); mResultThread.sleep(PIPELINE_WARMUP_TIME_MS); } @@ -2969,6 +2998,11 @@ public class ItsService extends Service implements SensorEventListener { long timeout = TIMEOUT_CALLBACK * 1000; waitForCallbacks(timeout); + if (mExtensionPreviewImageReader != null) { + mExtensionPreviewImageReader.close(); + mExtensionPreviewImageReader = null; + } + // Close session and wait until session is fully closed mExtensionSession.close(); sessionListener.getStateWaiter().waitForState( @@ -3669,6 +3703,41 @@ public class ItsService extends Service implements SensorEventListener { } } + private class ScalerCropRegionListener extends CaptureResultListener { + private volatile Rect mScalerCropRegion = null; + + @Override + public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, + long timestamp, long frameNumber) { + } + + @Override + public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, + TotalCaptureResult result) { + try { + if (request == null || result == null) { + throw new ItsException("Request/Result is invalid"); + } + + Logt.i(TAG, buildLogString(result)); + + if (result.get(CaptureResult.SCALER_CROP_REGION) != null) { + mScalerCropRegion = result.get(CaptureResult.SCALER_CROP_REGION); + } + + } catch (ItsException e) { + throw new ItsRuntimeException("Error handling capture result", e); + } + } + + @Override + public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, + CaptureFailure failure) { + Logt.e(TAG, "Script error: capture failed"); + } + + } + private class AutoframingResultListener extends CaptureResultListener { private volatile boolean mStopped = false; diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java index 23919f34fcc..e0f53be0392 100644 --- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java +++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java @@ -61,6 +61,7 @@ import com.android.queryable.annotations.Query; import com.google.auto.value.AutoAnnotation; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.rules.TestRule; @@ -693,7 +694,7 @@ public final class BedsteadJUnit4 extends BlockJUnit4ClassRunner { } if (enterprisePolicy.delegatedScopes().length > 0) { - Set<String> newDelegatedScopes = Set.of(enterprisePolicy.delegatedScopes()); + ImmutableSet<String> newDelegatedScopes = ImmutableSet.copyOf(enterprisePolicy.delegatedScopes()); if (!delegatedScopes.isEmpty() && !delegatedScopes.containsAll(newDelegatedScopes)) { throw new IllegalStateException("Cannot merge multiple policies which define " diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java index ddfc0cd303c..1e6bdbe8ab8 100644 --- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java +++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java @@ -4030,14 +4030,14 @@ public final class DeviceState extends HarrierRule { .findFirst(); if (account.isPresent()) { - accounts(onUser).setFeatures(account.get(), Set.of(features)); + accounts(onUser).setFeatures(account.get(), new HashSet<>(Arrays.asList(features))); mAccounts.put(key, account.get()); TestApis.devicePolicy().calculateHasIncompatibleAccounts(); return account.get(); } AccountReference createdAccount = accounts(onUser).addAccount() - .features(Set.of(features)) + .features(new HashSet<>(Arrays.asList(features))) .add(); mCreatedAccounts.add(createdAccount); mAccounts.put(key, createdAccount); @@ -4182,7 +4182,7 @@ public final class DeviceState extends HarrierRule { private boolean trySetUserRestrictionWithDeviceOwner(String restriction) { ensureHasDeviceOwner(FailureMode.FAIL, /* isPrimary= */ false, EnsureHasDeviceOwner.HeadlessDeviceOwnerType.NONE, - /* affiliationIds= */ Set.of(), /* type= */ DeviceOwnerType.DEFAULT, + /* affiliationIds= */ new HashSet<>(), /* type= */ DeviceOwnerType.DEFAULT, EnsureHasDeviceOwner.DEFAULT_KEY, new TestAppProvider().query()); RemotePolicyManager dpc = deviceOwner(); @@ -4200,7 +4200,7 @@ public final class DeviceState extends HarrierRule { private boolean trySetUserRestrictionWithProfileOwner(UserReference onUser, String restriction) { ensureHasProfileOwner(onUser, /* isPrimary= */ false, /* isParentInstance= */ false, - /* affiliationIds= */ Set.of(), EnsureHasProfileOwner.DEFAULT_KEY, + /* affiliationIds= */ new HashSet<>(), EnsureHasProfileOwner.DEFAULT_KEY, new TestAppProvider().query()); RemotePolicyManager dpc = profileOwner(onUser); @@ -4218,7 +4218,7 @@ public final class DeviceState extends HarrierRule { private boolean tryClearUserRestrictionWithDeviceOwner(String restriction) { ensureHasDeviceOwner(FailureMode.FAIL, /* isPrimary= */ false, EnsureHasDeviceOwner.HeadlessDeviceOwnerType.NONE, - /* affiliationIds= */ Set.of(), /* type= */ DeviceOwnerType.DEFAULT, + /* affiliationIds= */ new HashSet<>(), /* type= */ DeviceOwnerType.DEFAULT, EnsureHasDeviceOwner.DEFAULT_KEY, new TestAppProvider().query()); RemotePolicyManager dpc = deviceOwner(); @@ -4236,7 +4236,7 @@ public final class DeviceState extends HarrierRule { private boolean tryClearUserRestrictionWithProfileOwner(UserReference onUser, String restriction) { ensureHasProfileOwner(onUser, /* isPrimary= */ false, /* isParentInstance= */ false, - /* affiliationIds= */ Set.of(), EnsureHasProfileOwner.DEFAULT_KEY, + /* affiliationIds= */ new HashSet<>(), EnsureHasProfileOwner.DEFAULT_KEY, new TestAppProvider().query()); RemotePolicyManager dpc = profileOwner(onUser); diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/Policy.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/Policy.java index c519b7ac862..a63932d617a 100644 --- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/Policy.java +++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/Policy.java @@ -439,11 +439,11 @@ public final class Policy { existingAnnotations.length + 1); newAnnotations[newAnnotations.length - 1] = ensureHasDevicePolicyManagerRoleHolder( roleHolderUser, /* isPrimary= */ true); - return Set.of(annotation, + return new HashSet<>(Arrays.asList(annotation, new DynamicParameterizedAnnotation( annotation.annotationType().getSimpleName() + "_DPMRH", newAnnotations - )); + ))); }; } @@ -894,7 +894,7 @@ public final class Policy { for (Annotation annotation : annotations) { shadowingAnnotations.addAll( - sReverseShadowMap.getOrDefault(annotation.annotationType(), Set.of())); + sReverseShadowMap.getOrDefault(annotation.annotationType(), new HashSet<>())); } annotations.removeIf(a -> shadowingAnnotations.contains(a.annotationType())); diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/accounts/AccountBuilder.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/accounts/AccountBuilder.java index 0a4930f930c..26b2127757c 100644 --- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/accounts/AccountBuilder.java +++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/accounts/AccountBuilder.java @@ -25,6 +25,7 @@ import com.android.bedstead.nene.utils.Poll; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -102,7 +103,7 @@ public final class AccountBuilder { * Add features to the account. */ public AccountBuilder addFeatures(String... feature) { - mFeatures.addAll(Set.of(feature)); + mFeatures.addAll(new HashSet<>(Arrays.asList(feature))); return this; } 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 9c257af607f..8406a80b440 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 @@ -59,6 +59,7 @@ import com.android.bedstead.nene.utils.Versions; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; @@ -338,7 +339,7 @@ public final class Users { private UserType managedProfileUserType() { UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType(); managedProfileMutableUserType.mName = MANAGED_PROFILE_TYPE_NAME; - managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.PROFILE); + managedProfileMutableUserType.mBaseType = new HashSet<>(Arrays.asList(UserType.BaseType.PROFILE)); managedProfileMutableUserType.mEnabled = true; managedProfileMutableUserType.mMaxAllowed = -1; managedProfileMutableUserType.mMaxAllowedPerParent = 1; @@ -349,7 +350,7 @@ public final class Users { UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType(); managedProfileMutableUserType.mName = SYSTEM_USER_TYPE_NAME; managedProfileMutableUserType.mBaseType = - Set.of(UserType.BaseType.FULL, UserType.BaseType.SYSTEM); + new HashSet<>(Arrays.asList(UserType.BaseType.FULL, UserType.BaseType.SYSTEM)); managedProfileMutableUserType.mEnabled = true; managedProfileMutableUserType.mMaxAllowed = -1; managedProfileMutableUserType.mMaxAllowedPerParent = -1; @@ -359,7 +360,7 @@ public final class Users { private UserType secondaryUserType() { UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType(); managedProfileMutableUserType.mName = SECONDARY_USER_TYPE_NAME; - managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.FULL); + managedProfileMutableUserType.mBaseType = new HashSet<>(Arrays.asList(UserType.BaseType.FULL)); managedProfileMutableUserType.mEnabled = true; managedProfileMutableUserType.mMaxAllowed = -1; managedProfileMutableUserType.mMaxAllowedPerParent = -1; diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTypeTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTypeTest.java index d05e0914027..603bdaa3f22 100644 --- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTypeTest.java +++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTypeTest.java @@ -22,6 +22,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.Arrays; +import java.util.HashSet; import java.util.Set; @RunWith(JUnit4.class) @@ -50,7 +52,7 @@ public class UserTypeTest { @Test public void baseType_returnsBaseType() { UserType.MutableUserType mutableUserType = new UserType.MutableUserType(); - mutableUserType.mBaseType = Set.of(UserType.BaseType.FULL); + mutableUserType.mBaseType = new HashSet<>(Arrays.asList(UserType.BaseType.FULL)); UserType userType = new UserType(mutableUserType); assertThat(userType.baseType()).containsExactly(UserType.BaseType.FULL); diff --git a/common/device-side/bedstead/remoteaccountauthenticator/src/main/java/com/android/bedstead/remoteaccountauthenticator/RemoteAccountAuthenticator.java b/common/device-side/bedstead/remoteaccountauthenticator/src/main/java/com/android/bedstead/remoteaccountauthenticator/RemoteAccountAuthenticator.java index 9c813c15dc6..47caa4df661 100644 --- a/common/device-side/bedstead/remoteaccountauthenticator/src/main/java/com/android/bedstead/remoteaccountauthenticator/RemoteAccountAuthenticator.java +++ b/common/device-side/bedstead/remoteaccountauthenticator/src/main/java/com/android/bedstead/remoteaccountauthenticator/RemoteAccountAuthenticator.java @@ -31,6 +31,8 @@ import com.android.bedstead.testapp.TestAppProvider; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -42,9 +44,9 @@ import java.util.stream.Stream; public final class RemoteAccountAuthenticator extends TestAppInstance { // TODO(263350665): Query account types from xml - private static final Set<String> ACCOUNT_TYPES = Set.of( + private static final Set<String> ACCOUNT_TYPES = new HashSet<>(Arrays.asList( "com.android.bedstead.remoteaccountauthenticator.account" - ); + )); private static final String REMOTE_ACCOUNT_AUTHENTICATOR_PACKAGE_NAME = "com.android.RemoteAccountAuthenticator"; diff --git a/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppAccountAuthenticator.java b/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppAccountAuthenticator.java index 558215ee20b..6ed639d119e 100644 --- a/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppAccountAuthenticator.java +++ b/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppAccountAuthenticator.java @@ -26,6 +26,7 @@ import android.os.Bundle; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.Set; /** @@ -132,7 +133,7 @@ public final class TestAppAccountAuthenticator extends AbstractAccountAuthentica hasFeatures = false; } else { hasFeatures = Arrays.asList(accountManager.getUserData(account, "features") - .split(",")).containsAll(Set.of(features)); + .split(",")).containsAll(new HashSet<>(Arrays.asList(features))); } Bundle result = new Bundle(); diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/ScreenDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/ScreenDeviceInfo.java index a44127474fc..20ddca1b778 100644 --- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/ScreenDeviceInfo.java +++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/ScreenDeviceInfo.java @@ -18,17 +18,23 @@ package com.android.compatibility.common.deviceinfo; import android.app.Activity; import android.content.Context; import android.content.res.Configuration; +import android.hardware.devicestate.DeviceStateManager; +import android.os.Build; import android.os.Bundle; -import android.server.wm.jetpack.utils.SidecarUtil; import android.server.wm.jetpack.utils.ExtensionUtil; +import android.server.wm.jetpack.utils.SidecarUtil; import android.server.wm.jetpack.utils.Version; import android.util.DisplayMetrics; import android.view.Display; import android.view.WindowManager; +import androidx.annotation.RequiresApi; + import com.android.compatibility.common.util.DeviceInfoStore; import com.android.compatibility.common.util.DummyActivity; +import java.io.IOException; + /** * Screen device info collector. */ @@ -39,6 +45,7 @@ public final class ScreenDeviceInfo extends DeviceInfo { DisplayMetrics metrics = new DisplayMetrics(); WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); + Display display = windowManager.getDefaultDisplay(); display.getRealMetrics(metrics); @@ -55,6 +62,11 @@ public final class ScreenDeviceInfo extends DeviceInfo { // Add WindowManager Jetpack Library version and available display features. addDisplayFeaturesIfPresent(store); + + // Add device states from DeviceStateManager if available. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + addDeviceStatesIfAvailable(store); + } } private void addDisplayFeaturesIfPresent(DeviceInfoStore store) throws Exception { @@ -85,6 +97,17 @@ public final class ScreenDeviceInfo extends DeviceInfo { } } + @RequiresApi(Build.VERSION_CODES.S) + private void addDeviceStatesIfAvailable(DeviceInfoStore store) throws IOException { + DeviceStateManager deviceStateManager = getContext().getSystemService( + DeviceStateManager.class); + + // Get the supported device states on device if DeviceStateManager is available + if (deviceStateManager != null) { + store.addArrayResult("device_states", deviceStateManager.getSupportedStates()); + } + } + private static String getScreenSize(Configuration configuration) { int screenLayout = configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; String screenSize = String.format("0x%x", screenLayout); diff --git a/common/device-side/interactive/src/main/java/com/android/interactive/Step.java b/common/device-side/interactive/src/main/java/com/android/interactive/Step.java index abe25f18aac..faf347d571f 100644 --- a/common/device-side/interactive/src/main/java/com/android/interactive/Step.java +++ b/common/device-side/interactive/src/main/java/com/android/interactive/Step.java @@ -22,6 +22,8 @@ import static com.android.bedstead.nene.permissions.CommonPermissions.SYSTEM_APP import static com.android.interactive.Automator.AUTOMATION_FILE; import android.graphics.PixelFormat; +import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -247,27 +249,32 @@ public abstract class Step<E> { * Adds a button to the interaction prompt. */ protected void addButton(String title, Runnable onClick) { - Button btn = new Button(TestApis.context().instrumentedContext()); - btn.setText(title); - btn.setOnClickListener(v -> onClick.run()); - - GridLayout layout = mInstructionView.findViewById(R.id.buttons); - layout.addView(btn); + // Push to UI thread to avoid animation issues when adding the button + new Handler(Looper.getMainLooper()).post(() -> { + Button btn = new Button(TestApis.context().instrumentedContext()); + btn.setText(title); + btn.setOnClickListener(v -> onClick.run()); + + GridLayout layout = mInstructionView.findViewById(R.id.buttons); + layout.addView(btn); + }); } /** * Adds small button with a single up/down arrow, used for moving the text box to the * bottom of the screen in case it covers some critical area of the app */ - protected void addSwapButton() { - Button btn = new Button(TestApis.context().instrumentedContext()); - // up/down arrow - btn.setText("\u21F5"); - btn.setOnClickListener(v -> swap()); - - GridLayout layout = mInstructionView.findViewById(R.id.buttons); - layout.addView(btn); + // Push to UI thread to avoid animation issues when adding the button + new Handler(Looper.getMainLooper()).post(() -> { + Button btn = new Button(TestApis.context().instrumentedContext()); + // up/down arrow + btn.setText("\u21F5"); + btn.setOnClickListener(v -> swap()); + + GridLayout layout = mInstructionView.findViewById(R.id.buttons); + layout.addView(btn); + }); } /** diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java index 9b82b4d71e1..1470f45be39 100644 --- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java +++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java @@ -116,8 +116,9 @@ public abstract class BaseDefaultPermissionGrantPolicyTest extends BusinessLogic addSplitFromNonDangerousPermissions(packagesToVerify, pregrantUidStates); } - if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU) - || ApiLevelUtil.codenameStartsWith("T")) { + if ((ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU) + || ApiLevelUtil.codenameStartsWith("T")) + && runtimePermNames.contains(Manifest.permission.POST_NOTIFICATIONS)) { addImplicitlyGrantedPermission(Manifest.permission.POST_NOTIFICATIONS, Build.VERSION_CODES.TIRAMISU, packagesToVerify, pregrantUidStates); } diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/PermissionUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/PermissionUtils.java new file mode 100644 index 00000000000..26f60560ce8 --- /dev/null +++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/PermissionUtils.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compatibility.common.util; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.permissionToOp; +import static android.content.pm.PackageManager.GET_PERMISSIONS; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity; +import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.AppOpsManager; +import android.app.UiAutomation; +import android.app.role.RoleManager; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Assert; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +/** + * Utility methods for permission-related functionality. + */ +public final class PermissionUtils { + private static final UiAutomation sUiAutomation = + InstrumentationRegistry.getInstrumentation().getUiAutomation(); + private static final String PLATFORM_PACKAGE_NAME = "android"; + + private static final String LOG_TAG = "PermissionUtils"; + + /** + * Checks if a permission is granted for a package. + * + * <p>This correctly handles pre-M apps by checking the app-ops instead.</p> + * + * @param packageName The package that might have the permission granted + * @param permission The permission that might be granted + * @return {@code true} iff the permission and app op are granted + */ + public static boolean isPermissionAndAppOpGranted(@NonNull String packageName, + @NonNull String permission) throws Exception { + if (!isPermissionGranted(packageName, permission)) { + return false; + } + return getAppOp(packageName, permission) != MODE_IGNORED; + } + + /** + * Grant a permission to an app. + * + * <p>This correctly handles pre-M apps by setting the app-ops.</p> + * + * @param packageName The app that should have the permission granted + * @param permission The permission to grant + */ + @TargetApi(28) + public static void grantPermissionAndAppOp(@NonNull String packageName, + @NonNull String permission) { + sUiAutomation.grantRuntimePermission(packageName, permission); + + setAppOpByPermission(packageName, permission, MODE_ALLOWED); + } + + /** + * Revoke a permission from an app. + * + * <p>This correctly handles pre-M apps by setting the app-ops.</p> + * + * @param packageName The app that should have the permission revoked + * @param permission The permission to revoke + */ + @TargetApi(28) + public static void revokePermissionAndAppOp(@NonNull String packageName, + @NonNull String permission) { + sUiAutomation.revokeRuntimePermission(packageName, permission); + + setAppOpByPermission(packageName, permission, MODE_IGNORED); + } + + /** + * Get all permissions an app requests. This includes the split permissions. + * + * @param packageName The package that requests the permissions. + * @return The permissions requested by the app + */ + @NonNull + public static List<String> getPermissions(@NonNull String packageName) + throws Exception { + PackageInfo appInfo = getTargetContext().getPackageManager().getPackageInfo(packageName, + GET_PERMISSIONS); + + return appInfo.requestedPermissions == null + ? Collections.emptyList() + : Arrays.asList(appInfo.requestedPermissions); + } + + // Enforce non instantiability with a private constructor + private PermissionUtils() { + } + + /** + * Get the state of an app-op. + * + * @param packageName The package for which the app-op is retrieved + * @param permission The permission to which the app-op is associated + * @return the app-op mode + */ + @TargetApi(29) + private static int getAppOp(@NonNull String packageName, @NonNull String permission) + throws Exception { + return callWithShellPermissionIdentity( + () -> getTargetContext().getSystemService(AppOpsManager.class).unsafeCheckOpRaw( + permissionToOp(permission), + getTargetContext().getPackageManager().getPackageUid( + packageName, 0), packageName)); + } + + /** + * Set a new state for an app-op + * + * <p>Uses the permission-name for new state.</p> + * + * @param packageName The package for which the app-op is retrieved + * @param permission The permission to which the app-op is associated + * @param mode The new mode + */ + @TargetApi(24) + private static void setAppOpByPermission(@NonNull String packageName, + @NonNull String permission, int mode) { + setAppOpByName(packageName, permissionToOp(permission), mode); + } + + /** + * Set a new state for an app-op (using the app-op-name) + * + * @param packageName The package for which the app-op is retrieved + * @param op The name of the op + * @param mode The new mode + */ + @TargetApi(24) + private static void setAppOpByName(@NonNull String packageName, @NonNull String op, int mode) { + runWithShellPermissionIdentity( + () -> getTargetContext().getSystemService(AppOpsManager.class).setUidMode(op, + getTargetContext().getPackageManager() + .getPackageUid(packageName, 0), mode)); + } + + /** + * Checks a permission. Does <u>not</u> check the appOp. + * + * <p>Users should use {@link #isPermissionAndAppOpGranted} instead.</p> + * + * @param packageName The package that might have the permission granted + * @param permission The permission that might be granted + * @return {@code true} iff the permission is granted + */ + private static boolean isPermissionGranted(@NonNull String packageName, + @NonNull String permission) { + return getTargetContext().getPackageManager().checkPermission(permission, packageName) + == PERMISSION_GRANTED; + } + + /** + * Returns if the device is a handheld device. + */ + @TargetApi(23) + public static boolean isHandheld(Context context) { + final PackageManager pm = context.getPackageManager(); + return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) + && !pm.hasSystemFeature(PackageManager.FEATURE_WATCH) + && !pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION) + && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } + + /** + * Get package names of the applications hlding the role. + * + * @param roleName the name of the role to get the role holder for + * @return a list of package names of the role holders, or an empty list if none. + */ + @TargetApi(29) + @NonNull + public static List<String> getRoleHolders(String roleName) throws Exception { + return callWithShellPermissionIdentity(() -> getTargetContext() + .getSystemService(RoleManager.class).getRoleHolders(roleName), + Manifest.permission.MANAGE_ROLE_HOLDERS); + } + + /** + * Returns the app holding the specified role, or null if no app has the role. If more than one + * app holds the role, throws an {@link IllegalStateException}. + * + * @param roleName The target role name. + * @return The package name of the app holding the role, or null if not app has the role. + */ + @Nullable + public static String getRoleHolder(String roleName) throws Exception { + List<String> packageNames = getRoleHolders(roleName); + if (packageNames.isEmpty()) { + return null; + } else if (packageNames.size() == 1) { + return packageNames.get(0); + } else { + throw new IllegalStateException("Expected only 1 package to hold the role \"" + + roleName + "\" but found " + packageNames.size() + ": " + packageNames); + } + } + + /** + * Check if it is a privileged permission. + * + * @return {@code true} iff it is a privileged permission + */ + @TargetApi(28) + public static boolean isPrivilegedPerm(PermissionInfo permissionInfo) { + return permissionInfo != null + && ((permissionInfo.getProtection() & PermissionInfo.PROTECTION_SIGNATURE) != 0) + && ((permissionInfo.getProtectionFlags() + & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0); + } + + /** + * Check if a package has any privileged permission granted. + * + * @param packageName The name of the package. + * @return {@code true} if the package has any privileged permission granted. + */ + public static boolean isPkgHasPrivilegedPermGranted(@NonNull String packageName) + throws Exception { + final PackageManager pm = getTargetContext().getPackageManager(); + PackageInfo pkgInfo = pm.getPackageInfo(packageName, GET_PERMISSIONS); + if (pkgInfo.requestedPermissions == null || pkgInfo.requestedPermissionsFlags == null) { + return false; + } + Assert.assertEquals( + String.format(Locale.US, + "[PackageInfo Error]: package(%s) requestedPermissions.length(%d) does " + + "not equal to " + + "requestedPermissionsFlags.length(%d)", + pkgInfo.packageName, + pkgInfo.requestedPermissions.length, + pkgInfo.requestedPermissionsFlags.length), + pkgInfo.requestedPermissions.length, + pkgInfo.requestedPermissionsFlags.length); + for (int i = 0; i < pkgInfo.requestedPermissions.length; i++) { + if ((pkgInfo.requestedPermissionsFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED) + != 0) { + try { + PermissionInfo permissionInfo = pm.getPermissionInfo( + pkgInfo.requestedPermissions[i], 0); + if (permissionInfo != null) { + return isPrivilegedPerm(permissionInfo); + } + } catch (PackageManager.NameNotFoundException e) { + Log.i(LOG_TAG, String.format("Permission %s not found", + pkgInfo.requestedPermissions[i]), e); + } + } + } + return false; + } + + /** + * Check if a package is a platform signed app. + * Compare the given app cert with the cert of "android" package on the device. + * If both are same, then app is platform signed. + * + * @param packageName The name of the package. + * @return {@code true} if the package is a platform signed app. + */ + public static boolean isPlatformSigned(@NonNull String packageName) { + final PackageManager pm = getTargetContext().getPackageManager(); + return pm.checkSignatures(packageName, PLATFORM_PACKAGE_NAME) + == PackageManager.SIGNATURE_MATCH; + } + + private static Context getTargetContext() { + return InstrumentationRegistry.getInstrumentation().getTargetContext(); + } +} diff --git a/hostsidetests/angle/Android.bp b/hostsidetests/angle/Android.bp index 4b699ade0f1..bf21e2ae777 100644 --- a/hostsidetests/angle/Android.bp +++ b/hostsidetests/angle/Android.bp @@ -35,7 +35,6 @@ java_test_host { ":CtsAngleDriverTestCases", ":CtsAngleDriverTestCasesSecondary", ":CtsAngleDumpsysGpuTestApp", - ":CtsAngleGameDriverTestCases", ], per_testcase_directory: true, } diff --git a/hostsidetests/angle/app/gameDriverTest/Android.bp b/hostsidetests/angle/app/gameDriverTest/Android.bp deleted file mode 100644 index 749f8cf8835..00000000000 --- a/hostsidetests/angle/app/gameDriverTest/Android.bp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -android_test_helper_app { - name: "CtsAngleGameDriverTestCases", - defaults: ["cts_support_defaults"], - srcs: [ - "src/**/*.java", - ], - // tag this module as a cts test artifact - test_suites: [ - "cts", - "general-tests", - ], - compile_multilib: "both", - static_libs: [ - "ctstestrunner-axt", - "androidx.test.rules", - "AngleIntegrationTestCommon", - ], -} diff --git a/hostsidetests/angle/app/gameDriverTest/AndroidManifest.xml b/hostsidetests/angle/app/gameDriverTest/AndroidManifest.xml deleted file mode 100755 index 7b6b10a80a2..00000000000 --- a/hostsidetests/angle/app/gameDriverTest/AndroidManifest.xml +++ /dev/null @@ -1,38 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -# Copyright (C) 2021 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.angleintegrationtest.gamedrivertest" - android:targetSandboxVersion="2"> - - <application android:debuggable="true" - android:appCategory="game"> - <uses-library android:name="android.test.runner"/> - - <activity android:name="com.android.angleintegrationtest.common.AngleIntegrationTestActivity" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - </application> - - <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.angleintegrationtest.gamedrivertest"/> - -</manifest> diff --git a/hostsidetests/angle/app/gameDriverTest/src/com/android/angleIntegrationTest/gameDriverTest/AngleDriverTestActivity.java b/hostsidetests/angle/app/gameDriverTest/src/com/android/angleIntegrationTest/gameDriverTest/AngleDriverTestActivity.java deleted file mode 100644 index cec4d2ae999..00000000000 --- a/hostsidetests/angle/app/gameDriverTest/src/com/android/angleIntegrationTest/gameDriverTest/AngleDriverTestActivity.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.angleintegrationtest.gamedrivertest; - -import static org.junit.Assert.fail; - -import androidx.test.runner.AndroidJUnit4; - -import com.android.angleintegrationtest.common.GlesView; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class AngleDriverTestActivity { - - private final String mTAG = this.getClass().getSimpleName(); - - private void validateDeveloperOption(boolean angleEnabled) throws Exception { - GlesView glesView = new GlesView(); - - if (!glesView.validateDeveloperOption(angleEnabled)) { - if (angleEnabled) { - String renderer = glesView.getRenderer(); - fail("Failure - ANGLE was not loaded: '" + renderer + "'"); - } else { - String renderer = glesView.getRenderer(); - fail("Failure - ANGLE was loaded: '" + renderer + "'"); - } - } - } - - @Test - public void testUseDefaultDriver() throws Exception { - validateDeveloperOption(false); - } - - @Test - public void testUseAngleDriver() throws Exception { - validateDeveloperOption(true); - } - - @Test - public void testUseNativeDriver() throws Exception { - validateDeveloperOption(false); - } -} diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java index 1cc73771a82..5a47f0c1053 100644 --- a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java +++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java @@ -41,15 +41,12 @@ class CtsAngleCommon { static final String ANGLE_DRIVER_TEST_PKG = "com.android.angleintegrationtest.drivertest"; static final String ANGLE_DRIVER_TEST_SEC_PKG = "com.android.angleintegrationtest.drivertestsecondary"; - static final String ANGLE_GAME_DRIVER_TEST_PKG = - "com.android.angleintegrationtest.gamedrivertest"; static final String ANGLE_DRIVER_TEST_CLASS = "AngleDriverTestActivity"; static final String ANGLE_DRIVER_TEST_DEFAULT_METHOD = "testUseDefaultDriver"; static final String ANGLE_DRIVER_TEST_ANGLE_METHOD = "testUseAngleDriver"; static final String ANGLE_DRIVER_TEST_NATIVE_METHOD = "testUseNativeDriver"; static final String ANGLE_DRIVER_TEST_APP = "CtsAngleDriverTestCases.apk"; static final String ANGLE_DRIVER_TEST_SEC_APP = "CtsAngleDriverTestCasesSecondary.apk"; - static final String ANGLE_GAME_DRIVER_TEST_APP = "CtsAngleGameDriverTestCases.apk"; static final String ANGLE_DUMPSYS_GPU_TEST_PKG = "com.android.angleintegrationtest.dumpsysgputest"; static final String ANGLE_DUMPSYS_GPU_TEST_CLASS = "AngleDumpsysGpuTestActivity"; @@ -160,26 +157,6 @@ class CtsAngleCommon { device.executeShellCommand("setprop " + property + " " + value); } - static void setGameModeBatteryConfig(ITestDevice device, String packageName, boolean useAngle) - throws Exception { - device.executeShellCommand("device_config put game_overlay " + packageName - + " mode=3,useAngle=" + Boolean.toString(useAngle)); - } - - static void setGameModeStandardConfig(ITestDevice device, String packageName, boolean useAngle) - throws Exception { - device.executeShellCommand("device_config put game_overlay " + packageName - + " mode=1,useAngle=" + Boolean.toString(useAngle)); - } - - static void setGameModeBattery(ITestDevice device, String packageName) throws Exception { - device.executeShellCommand("cmd game mode battery " + packageName); - } - - static void setGameModeStandard(ITestDevice device, String packageName) throws Exception { - device.executeShellCommand("cmd game mode standard " + packageName); - } - /** * Find and parse the `dumpsys gpu` output for the specified package. * diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java index 1d7548948d4..7484270dfe5 100644 --- a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java +++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java @@ -89,7 +89,6 @@ public class CtsAngleDeveloperOptionHostTest extends BaseHostJUnit4Test { stopPackage(getDevice(), ANGLE_DRIVER_TEST_PKG); stopPackage(getDevice(), ANGLE_DRIVER_TEST_SEC_PKG); - stopPackage(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG); } @After @@ -363,96 +362,6 @@ public class CtsAngleDeveloperOptionHostTest extends BaseHostJUnit4Test { } /** - * Test ANGLE is loaded when the Battery Game Mode includes 'useAngle=true'. - */ - @Test - public void testGameModeBatteryUseAngleDriver() throws Exception { - Assume.assumeTrue(isAngleInstalled(getDevice())); - - installApp(ANGLE_GAME_DRIVER_TEST_APP); - - setGameModeBatteryConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, true); - setGameModeBattery(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG); - - runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG, - ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS, - ANGLE_DRIVER_TEST_ANGLE_METHOD); - } - - /** - * Test ANGLE is loaded when the Standard Game Mode includes 'useAngle=true'. - */ - @Test - public void testGameModeStandardUseAngleDriver() throws Exception { - Assume.assumeTrue(isAngleInstalled(getDevice())); - - installApp(ANGLE_GAME_DRIVER_TEST_APP); - - setGameModeStandardConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, true); - setGameModeStandard(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG); - - runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG, - ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS, - ANGLE_DRIVER_TEST_ANGLE_METHOD); - } - - /** - * Test setting the Game Mode to use ANGLE ('useAngle=true') and then overriding that to use the - * native driver with the Global.Settings loads the native driver. - */ - @Test - public void testGameModeBatteryUseAngleOverrideWithNative() throws Exception { - Assume.assumeTrue(isAngleInstalled(getDevice())); - Assume.assumeFalse(isAngleOnlySystem(getDevice())); - - installApp(ANGLE_GAME_DRIVER_TEST_APP); - - // Set Game Mode to use ANGLE and verify ANGLE is loaded. - setGameModeBatteryConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, true); - setGameModeBattery(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG); - - runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG, - ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS, - ANGLE_DRIVER_TEST_ANGLE_METHOD); - - // Set Global.Settings to use the native driver and verify the native driver is loaded. - setAndValidateAngleDevOptionPkgDriver(ANGLE_GAME_DRIVER_TEST_PKG, - sDriverGlobalSettingMap.get(OpenGlDriverChoice.NATIVE)); - - runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG, - ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS, - ANGLE_DRIVER_TEST_NATIVE_METHOD); - } - - /** - * Test setting the Game Mode to not use ANGLE ('useAngle=false') and then overriding that to - * use ANGLE with the Global.Settings loads ANGLE. - */ - @Test - public void testGameModeBatteryDontUseAngleOverrideWithAngle() throws Exception { - Assume.assumeTrue(isAngleInstalled(getDevice())); - final String testMethod = getTestMethod(getDevice()); - - installApp(ANGLE_GAME_DRIVER_TEST_APP); - - // Set Game Mode to *not* use ANGLE and verify the native driver is loaded when ANGLE is not - // the system driver. - setGameModeBatteryConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, false); - setGameModeBattery(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG); - - runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG, - ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS, testMethod); - - // Set Global.Settings to use ANGLE and verify ANGLE is loaded. - setAndValidateAngleDevOptionPkgDriver(ANGLE_GAME_DRIVER_TEST_PKG, - sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE)); - - runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG, - ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS, - ANGLE_DRIVER_TEST_ANGLE_METHOD); - } - - /** * Test that the `dumpsys gpu` correctly indicates `angleInUse = 1` when ANGLE is enabled. */ @Test diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java index 51daffd70a3..d31f7d83342 100644 --- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java +++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java @@ -40,6 +40,7 @@ public final class CompatChangesValidConfigTest extends CompatChangeGatingTestCa private static final long RESTRICT_STORAGE_ACCESS_FRAMEWORK = 141600225L; private static final long SPLIT_AS_STREAM_RETURNS_SINGLE_EMPTY_STRING = 288845345L; + private static final long PRIORITY_QUEUE_OFFER_NON_COMPARABLE_ONE_ELEMENT = 289878283L; private static final String FEATURE_WATCH = "android.hardware.type.watch"; private static final Set<String> OVERRIDES_ALLOWLIST = ImmutableSet.of( @@ -174,6 +175,10 @@ public final class CompatChangesValidConfigTest extends CompatChangeGatingTestCa // This feature is enabled only from U for apps targeting SDK 34+, see b/288845345 changes.removeIf(c -> c.changeId == SPLIT_AS_STREAM_RETURNS_SINGLE_EMPTY_STRING); + // Exclude PRIORITY_QUEUE_OFFER_NON_COMPARABLE_ONE_ELEMENT + // This feature is enabled only from U for apps targeting SDK 34+, see b/297482242 + changes.removeIf(c -> c.changeId == PRIORITY_QUEUE_OFFER_NON_COMPARABLE_ONE_ELEMENT); + return changes; } diff --git a/hostsidetests/appsecurity/Android.bp b/hostsidetests/appsecurity/Android.bp index 100e5ede8b9..9a4a8c402e9 100644 --- a/hostsidetests/appsecurity/Android.bp +++ b/hostsidetests/appsecurity/Android.bp @@ -171,3 +171,40 @@ filegroup { name: "CtsHostsideTestsAppSecurityUtil", srcs: ["src/android/appsecurity/cts/Utils.java"], } + +// The ApexSignatureVerificationTest is split into a separate java_test_host +// to make it possible to run this test as part of GTS as well. +// See b/301094654 +java_test_host { + name: "GtsApexSignatureVerificationTest", + defaults: ["cts_defaults"], + + srcs: [ + "src/android/appsecurity/cts/ApexSignatureVerificationTest.java", + ], + libs: [ + "cts-tradefed", + "tradefed", + "compatibility-host-util", + "truth-prebuilt", + "hamcrest-library", + ], + + static_libs: [ + "CompatChangeGatingTestBase", + "CtsPkgInstallerConstants", + "cts-host-utils", + "cts-statsd-atom-host-test-utils", + "sts-host-util", + ], + + java_resource_dirs: ["res"], + + // tag this module as a cts test artifact + test_suites: [ + "general-tests", + "gts", + ], + per_testcase_directory: true, + test_config: "AndroidTest_ApexSignatureVerificationTest.xml", +} diff --git a/hostsidetests/appsecurity/AndroidTest_ApexSignatureVerificationTest.xml b/hostsidetests/appsecurity/AndroidTest_ApexSignatureVerificationTest.xml new file mode 100644 index 00000000000..d14b67e196a --- /dev/null +++ b/hostsidetests/appsecurity/AndroidTest_ApexSignatureVerificationTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Config for the GtsApexSignatureVerificationTest"> + <option name="test-suite-tag" value="gts" /> + <option name="config-descriptor:metadata" key="component" value="packagemanager" /> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="secondary_user" /> + <target_preparer class="android.appsecurity.cts.AppSecurityPreparer" /> + <!-- disable DeprecatedAbi warning --> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" /> + </target_preparer> + <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > + <option name="jar" value="GtsApexSignatureVerificationTest.jar" /> + </test> +</configuration> diff --git a/hostsidetests/appsecurity/res/apexsigverify/build.bazel.examples.apex.minimal.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/build.bazel.examples.apex.minimal.avbpubkey Binary files differnew file mode 100644 index 00000000000..e6ffe589d5a --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/build.bazel.examples.apex.minimal.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.adservices.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.adservices.avbpubkey Binary files differnew file mode 100644 index 00000000000..42885199567 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.adservices.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.product.test.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.product.test.avbpubkey Binary files differnew file mode 100644 index 00000000000..bef1df60203 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.product.test.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.system.test.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.system.test.avbpubkey Binary files differnew file mode 100644 index 00000000000..ef0438e6e1f --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.system.test.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.system_ext.test.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.system_ext.test.avbpubkey Binary files differnew file mode 100644 index 00000000000..0d978b8d7e3 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.system_ext.test.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.vendor.test.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.vendor.test.avbpubkey Binary files differnew file mode 100644 index 00000000000..3ad68fca581 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.vendor.test.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.appsearch.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.appsearch.avbpubkey Binary files differnew file mode 100644 index 00000000000..4e5acae9c1e --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.appsearch.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.btservices.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.btservices.avbpubkey Binary files differnew file mode 100644 index 00000000000..969211f93ec --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.btservices.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.car.framework.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.car.framework.avbpubkey Binary files differnew file mode 100644 index 00000000000..0b720b84598 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.car.framework.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.cellbroadcast.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.cellbroadcast.avbpubkey Binary files differnew file mode 100644 index 00000000000..a7f87c33477 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.cellbroadcast.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.compos.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.compos.avbpubkey Binary files differnew file mode 100644 index 00000000000..3f09680b7bc --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.compos.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.extservices.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.extservices.avbpubkey Binary files differnew file mode 100644 index 00000000000..f37d3e4a14d --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.extservices.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.geotz.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.geotz.avbpubkey Binary files differnew file mode 100644 index 00000000000..f705184b206 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.geotz.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.core_permissions.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.core_permissions.avbpubkey Binary files differnew file mode 100644 index 00000000000..b9164fb28f7 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.core_permissions.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.power.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.power.avbpubkey Binary files differnew file mode 100644 index 00000000000..3b6411d994b --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.power.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.sensors.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.sensors.avbpubkey Binary files differnew file mode 100644 index 00000000000..98dfb71b81f --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.sensors.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.thermal.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.thermal.avbpubkey Binary files differnew file mode 100644 index 00000000000..8f7cf72760d --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.thermal.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.usb.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.usb.avbpubkey Binary files differnew file mode 100644 index 00000000000..0302d6341a8 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.usb.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.vibrator.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.vibrator.avbpubkey Binary files differnew file mode 100644 index 00000000000..a6ca6303fa0 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.vibrator.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.wifi.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.wifi.avbpubkey Binary files differnew file mode 100644 index 00000000000..63fba77bb19 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.hardware.wifi.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.mediaprovider.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.mediaprovider.avbpubkey Binary files differnew file mode 100644 index 00000000000..c1b8dda34b3 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.mediaprovider.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.modules.updatablesharedlibs.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.modules.updatablesharedlibs.avbpubkey Binary files differnew file mode 100644 index 00000000000..e95ecbe86bc --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.modules.updatablesharedlibs.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.ondevicepersonalization.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.ondevicepersonalization.avbpubkey Binary files differnew file mode 100644 index 00000000000..4e74bb185bd --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.ondevicepersonalization.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.os.statsd.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.os.statsd.avbpubkey Binary files differnew file mode 100644 index 00000000000..d78af8b8bef --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.os.statsd.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.permission.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.permission.avbpubkey Binary files differnew file mode 100644 index 00000000000..9eaf8525963 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.permission.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.rkpd.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.rkpd.avbpubkey Binary files differnew file mode 100644 index 00000000000..94d4e7090f4 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.rkpd.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.scheduling.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.scheduling.avbpubkey Binary files differnew file mode 100644 index 00000000000..63bbfabc145 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.scheduling.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.uwb.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.uwb.avbpubkey Binary files differnew file mode 100644 index 00000000000..ccdd6d65809 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.uwb.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.vibrator.drv2624.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.vibrator.drv2624.avbpubkey Binary files differnew file mode 100644 index 00000000000..479a8686c7e --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.vibrator.drv2624.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.vibrator.sunfish.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.vibrator.sunfish.avbpubkey Binary files differnew file mode 100644 index 00000000000..497aa290ec1 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.vibrator.sunfish.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.virt.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.virt.avbpubkey Binary files differnew file mode 100644 index 00000000000..79ab8db4fc6 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.virt.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.vndk.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.vndk.avbpubkey Binary files differnew file mode 100644 index 00000000000..f408d2b4945 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.vndk.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.wifi.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.wifi.avbpubkey Binary files differnew file mode 100644 index 00000000000..e98ee34db08 --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.wifi.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.google.cf.apex.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.google.cf.apex.avbpubkey Binary files differnew file mode 100644 index 00000000000..1fc39e1175d --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.google.cf.apex.avbpubkey diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.google.emulated.camera.provider.hal.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.google.emulated.camera.provider.hal.avbpubkey Binary files differnew file mode 100644 index 00000000000..0c28e11c7ed --- /dev/null +++ b/hostsidetests/appsecurity/res/apexsigverify/com.google.emulated.camera.provider.hal.avbpubkey diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java index b73c8d75f99..844111e5dad 100644 --- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java +++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java @@ -30,14 +30,13 @@ import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.ZipUtil; -import org.hamcrest.CustomTypeSafeMatcher; -import org.hamcrest.Matcher; +import com.google.common.truth.Expect; + import org.junit.AfterClass; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ErrorCollector; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runner.RunWith; @@ -87,7 +86,7 @@ public class ApexSignatureVerificationTest extends BaseHostJUnit4Test { private ITestDevice mDevice; @Rule - public final ErrorCollector mErrorCollector = new ErrorCollector(); + public final Expect mExpect = Expect.create(); @Before public void setUp() throws Exception { @@ -153,8 +152,8 @@ public class ApexSignatureVerificationTest extends BaseHostJUnit4Test { while (it.hasNext()) { final File wellKnownKey = (File) it.next(); - verifyPubKey("must not use well known pubkey", pubKeyFile, - pubkeyShouldNotEqualTo(wellKnownKey)); + mExpect.withMessage(entry.getKey() + " must not use well known pubkey") + .that(areKeysMatching(pubKeyFile, wellKnownKey)).isFalse(); } } } @@ -192,7 +191,8 @@ public class ApexSignatureVerificationTest extends BaseHostJUnit4Test { try { apexes = mDevice.getActiveApexes(); for (ITestDevice.ApexInfo ap : apexes) { - if (!ap.sourceDir.startsWith("/data/")) { + // Compressed APEXes on /system are decompressed to /data/apex/decompressed + if (!ap.sourceDir.startsWith("/data/apex/active")) { mPreloadedApexPathMap.put(ap.name, ap.sourceDir); } } @@ -279,24 +279,14 @@ public class ApexSignatureVerificationTest extends BaseHostJUnit4Test { assertThat(mWellKnownKeyFileList).isNotEmpty(); } - private <T> void verifyPubKey(String reason, T actual, Matcher<? super T> matcher) { - mErrorCollector.checkThat(reason, actual, matcher); - } - - private static Matcher<File> pubkeyShouldNotEqualTo(File wellknownKey) { - return new CustomTypeSafeMatcher<File>("must not match well known key ") { - @Override - protected boolean matchesSafely(File actual) { - boolean isMatchWellknownKey = false; - try { - isMatchWellknownKey = FileUtil.compareFileContents(actual, wellknownKey); - } catch (IOException e) { - e.printStackTrace(); - } - // Assert fail if the keys matched - return !isMatchWellknownKey; - } - }; + private static boolean areKeysMatching(File pubkey, File wellknownKey) { + try { + return FileUtil.compareFileContents(pubkey, wellknownKey); + } catch (IOException e) { + throw new AssertionError( + "Failed to compare " + pubkey.getAbsolutePath() + " and " + + wellknownKey.getAbsolutePath()); + } } /** diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java index f712a95b1b8..a58bd7c476d 100644 --- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java +++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java @@ -33,6 +33,7 @@ import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +44,7 @@ import junitparams.Parameters; @Presubmit @RunWith(DeviceParameterizedRunner.class) @AppModeFull +@Ignore("b/303068306") public final class ApkVerityInstallTest extends BaseAppSecurityTest { private static final String PACKAGE_NAME = "android.appsecurity.cts.apkveritytestapp"; diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java index 04603b3f9ed..ec265ef24b5 100644 --- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java +++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java @@ -1468,12 +1468,12 @@ public class ClientTest { private TestResult getResult() { final TestResult result; try { - result = mResultQueue.poll(5, TimeUnit.SECONDS); + result = mResultQueue.poll(25, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } if (result == null) { - throw new IllegalStateException("Activity didn't receive a Result in 5 seconds"); + throw new IllegalStateException("Activity didn't receive a Result in 25 seconds"); } return result; } diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml index 03e4f7df0fb..315a1d28e3a 100644 --- a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml +++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml @@ -20,6 +20,7 @@ <uses-library android:name="android.test.runner" /> <activity android:name=".OverlayTargetActivity" android:exported="false" android:configChanges="@integer/config_changes_assets_paths" /> + <activity android:name=".SimpleActivity" android:exported="true" /> <service android:name=".OverlayTargetService" android:exported="false" /> </application> diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetTest.java b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetTest.java index 938f1c41ffb..7c8b9e078a8 100644 --- a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetTest.java +++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetTest.java @@ -89,7 +89,7 @@ public class OverlayTargetTest { public void overlayEnabled_activityInBackground_toForeground() throws Exception { final OverlayTargetActivity targetActivity = mActivityTestRule.getActivity(); // Activity goes into background - launchHome(); + launchSimpleActivity(); mInstrumentation.waitForIdleSync(); final CountDownLatch latch = new CountDownLatch(1); targetActivity.setConfigurationChangedCallback((activity, config) -> { @@ -137,9 +137,11 @@ public class OverlayTargetTest { () -> expected.equals(getStateForOverlay(overlayPackage))); } - private static void launchHome() { - SystemUtil.runShellCommand("am start -W -a android.intent.action.MAIN" - + " -c android.intent.category.HOME"); + private void launchSimpleActivity() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setClass(mInstrumentation.getTargetContext(), SimpleActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mInstrumentation.startActivitySync(intent); } private static String getStateForOverlay(String overlayPackage) { diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/SimpleActivity.java b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/SimpleActivity.java new file mode 100644 index 00000000000..e580927105a --- /dev/null +++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/SimpleActivity.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.cts.overlay.target; + +import android.app.Activity; + +/** + * Another activity doing nothing + */ +public class SimpleActivity extends Activity { +} diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java deleted file mode 100644 index 741f07778fd..00000000000 --- a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.cts.launchertests; - -import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertThrows; - -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.UserHandle; -import android.os.UserManager; -import android.support.test.uiautomator.UiDevice; -import android.text.TextUtils; - -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import com.android.bedstead.nene.utils.ShellCommand; -import com.android.compatibility.common.util.BlockingBroadcastReceiver; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; -import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * Test that runs {@link UserManager#trySetQuietModeEnabled(boolean, UserHandle)} API - * against valid target user. - */ -@RunWith(AndroidJUnit4.class) -public class QuietModeTest { - private static final String PARAM_TARGET_USER = "TARGET_USER"; - private static final String PARAM_ORIGINAL_DEFAULT_LAUNCHER = "ORIGINAL_DEFAULT_LAUNCHER"; - private static final ComponentName LAUNCHER_ACTIVITY = - new ComponentName( - "com.android.cts.launchertests.support", - "com.android.cts.launchertests.support.LauncherActivity"); - - private static final ComponentName COMMAND_RECEIVER = - new ComponentName( - "com.android.cts.launchertests.support", - "com.android.cts.launchertests.support.QuietModeCommandReceiver"); - private static final String EXTRA_FLAGS = "quiet_mode_flags"; - - private UserManager mUserManager; - private UserHandle mTargetUser; - private Context mContext; - private String mOriginalLauncher; - private UiDevice mUiDevice; - - @Before - public void setupUserManager() throws Exception { - mContext = InstrumentationRegistry.getTargetContext(); - mUserManager = mContext.getSystemService(UserManager.class); - } - - @Before - public void readParams() { - Context context = InstrumentationRegistry.getContext(); - Bundle arguments = InstrumentationRegistry.getArguments(); - UserManager userManager = context.getSystemService(UserManager.class); - final int userSn = Integer.parseInt(arguments.getString(PARAM_TARGET_USER)); - mTargetUser = userManager.getUserForSerialNumber(userSn); - mOriginalLauncher = arguments.getString(PARAM_ORIGINAL_DEFAULT_LAUNCHER); - } - - @Before - public void wakeupDeviceAndUnlock() throws Exception { - mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); - mUiDevice.wakeUp(); - mUiDevice.pressMenu(); - } - - @Before - @After - public void revertToDefaultLauncher() throws Exception { - if (TextUtils.isEmpty(mOriginalLauncher)) { - return; - } - setDefaultLauncher(InstrumentationRegistry.getInstrumentation(), mOriginalLauncher); - } - - @Test - public void testTryEnableQuietMode_defaultForegroundLauncher() throws Exception { - setTestAppAsDefaultLauncher(); - startLauncherActivityInTestApp(); - - Intent intent = trySetQuietModeEnabled(true); - assertNotNull("Failed to receive ACTION_MANAGED_PROFILE_UNAVAILABLE broadcast", intent); - assertTrue(mUserManager.isQuietModeEnabled(mTargetUser)); - - intent = trySetQuietModeEnabled(false); - assertNotNull("Failed to receive ACTION_MANAGED_PROFILE_AVAILABLE broadcast", intent); - assertFalse(mUserManager.isQuietModeEnabled(mTargetUser)); - } - - @Test - public void testTryEnableQuietMode_notForegroundLauncher() throws InterruptedException { - setTestAppAsDefaultLauncher(); - - assertThrows(SecurityException.class, () -> trySetQuietModeEnabled(true)); - assertFalse(mUserManager.isQuietModeEnabled(mTargetUser)); - } - - @Test - public void testTryEnableQuietMode_notDefaultLauncher() throws Exception { - startLauncherActivityInTestApp(); - - assertThrows(SecurityException.class, () -> trySetQuietModeEnabled(true)); - assertFalse(mUserManager.isQuietModeEnabled(mTargetUser)); - } - - @Test - public void testTryEnableQuietMode_noCredentialRequest() throws Exception { - setTestAppAsDefaultLauncher(); - startLauncherActivityInTestApp(); - - Intent intent = trySetQuietModeEnabled(true, - UserManager.QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED, true); - assertNotNull("Failed to receive ACTION_MANAGED_PROFILE_UNAVAILABLE broadcast", intent); - assertTrue(mUserManager.isQuietModeEnabled(mTargetUser)); - - if (!ShellCommand.builder("dumpsys device_policy").execute() - .contains("Keep profiles running: true")) { - waitForUserLocked(); - } - - intent = trySetQuietModeEnabled(false, - UserManager.QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED, false); - assertNull("Received ACTION_MANAGED_PROFILE_AVAILABLE broadcast", intent); - assertTrue(mUserManager.isQuietModeEnabled(mTargetUser)); - } - - private void waitForUserLocked() throws Exception { - // Should match a line in "dumpsys mount" output like this: - // Local unlocked users: [0, 10] - final Pattern p = Pattern.compile("Local unlocked users: \\[(.*)\\]"); - final long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(60); - while (System.nanoTime() < deadline) { - final String output = mUiDevice.executeShellCommand("dumpsys mount"); - final Matcher matcher = p.matcher(output); - assertTrue("Unexpected dupmsys mount output: " + output, matcher.find()); - final Set<Integer> unlockedUsers = Arrays.stream(matcher.group(1).split(", ")) - .map(Integer::valueOf) - .collect(Collectors.toSet()); - if (!unlockedUsers.contains(mTargetUser.getIdentifier())) { - return; - } - Thread.sleep(500); - } - fail("Cannot get the profile locked"); - } - - private Intent trySetQuietModeEnabled(boolean enabled, int flags, - boolean expectsCredentialsNotNeeded) throws Exception { - return trySetQuietModeEnabled(enabled, true, flags, expectsCredentialsNotNeeded); - } - - @Test - public void testTryEnableQuietMode() throws Exception { - setTestAppAsDefaultLauncher(); - startLauncherActivityInTestApp(); - - Intent intent = trySetQuietModeEnabled(true); - assertNotNull("Failed to receive ACTION_MANAGED_PROFILE_UNAVAILABLE broadcast", intent); - assertTrue(mUserManager.isQuietModeEnabled(mTargetUser)); - } - - @Test - public void testTryDisableQuietMode() throws Exception { - setTestAppAsDefaultLauncher(); - startLauncherActivityInTestApp(); - - Intent intent = trySetQuietModeEnabled(false); - assertNotNull("Failed to receive ACTION_MANAGED_PROFILE_AVAILABLE broadcast", intent); - assertFalse(mUserManager.isQuietModeEnabled(mTargetUser)); - } - - private Intent trySetQuietModeEnabled(boolean enabled) throws Exception { - return trySetQuietModeEnabled(enabled, false, 0, true); - } - - private Intent trySetQuietModeEnabled(boolean enabled, boolean hasFlags, int flags, - boolean expectsCredentialsNotNeeded) throws Exception { - final String action = enabled - ? Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE - : Intent.ACTION_MANAGED_PROFILE_AVAILABLE; - - BlockingBroadcastReceiver receiver = - new BlockingBroadcastReceiver(mContext, action); - try { - receiver.register(); - - boolean credentialNotNeeded = askLauncherSupportAppToSetQuietMode(enabled, hasFlags, - flags); - assertEquals(credentialNotNeeded, expectsCredentialsNotNeeded); - return receiver.awaitForBroadcast(); - } finally { - receiver.unregisterQuietly(); - } - } - - /** - * Ask launcher support test app to set quiet mode by sending broadcast. - * <p> - * We cannot simply make this package the launcher and call the API because instrumentation - * process would always considered to be in the foreground. The trick here is to send - * broadcast to another test app which is launcher itself and call the API through it. - * The receiver will then send back the result, and it should be either true, false or - * security-exception. - * <p> - * All the constants defined here should be aligned with - * com.android.cts.launchertests.support.QuietModeCommandReceiver. - */ - private boolean askLauncherSupportAppToSetQuietMode(boolean enabled, boolean hasFlags, int flags) throws Exception { - Intent intent = new Intent("toggle_quiet_mode"); - intent.setComponent(COMMAND_RECEIVER); - intent.putExtra("quiet_mode", enabled); - intent.putExtra(Intent.EXTRA_USER, mTargetUser); - if (hasFlags) { - intent.putExtra(EXTRA_FLAGS, flags); - } - - // Ask launcher support app to set quiet mode by sending broadcast. - LinkedBlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(); - mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - blockingQueue.offer(getResultData()); - } - }, null, 0, "", null); - - // Wait for the result. - String result = null; - for (int i = 0; i < 10; i++) { - // Broadcast won't be delivered when the device is sleeping, so wake up the device - // in between each attempt. - wakeupDeviceAndUnlock(); - result = blockingQueue.poll(10, TimeUnit.SECONDS); - if (!TextUtils.isEmpty(result)) { - break; - } - } - - // Parse the result. - assertNotNull(result); - if ("true".equalsIgnoreCase(result)) { - return true; - } else if ("false".equalsIgnoreCase(result)) { - return false; - } else if ("security-exception".equals(result)) { - throw new SecurityException(); - } - throw new IllegalStateException("Unexpected result : " + result); - } - - private void startActivitySync(String activity) throws Exception { - mUiDevice.executeShellCommand("am start -W -n " + activity); - } - - /** - * Start the launcher activity in the test app to make it foreground. - */ - private void startLauncherActivityInTestApp() throws Exception { - startActivitySync(LAUNCHER_ACTIVITY.flattenToString()); - } - - private void setTestAppAsDefaultLauncher() { - setDefaultLauncher(InstrumentationRegistry.getInstrumentation(), - LAUNCHER_ACTIVITY.getPackageName()); - } -} - diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java index 7aaacf39430..b66c2b42cd7 100644 --- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java +++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java @@ -241,6 +241,7 @@ public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest { @RequiresDevice @Test public void testAlwaysOnVpn() throws Exception { + assumeIsNotWatch(); int userId = getUserIdForAlwaysOnVpnTests(); installAppAsUser(VPN_APP_APK, userId); executeDeviceTestClassNoRestrictBackground(".AlwaysOnVpnTest", userId); @@ -253,6 +254,7 @@ public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest { @RequiresDevice @Test public void testAlwaysOnVpnLockDown() throws Exception { + assumeIsNotWatch(); int userId = getUserIdForAlwaysOnVpnTests(); installAppAsUser(VPN_APP_APK, userId); try { @@ -267,6 +269,7 @@ public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest { @RequiresDevice @Test public void testAlwaysOnVpnAcrossReboot() throws Exception { + assumeIsNotWatch(); int userId = getUserIdForAlwaysOnVpnTests(); try { installAppAsUser(VPN_APP_APK, userId); @@ -286,6 +289,7 @@ public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest { @RequiresDevice @Test public void testAlwaysOnVpnPackageUninstalled() throws Exception { + assumeIsNotWatch(); int userId = getUserIdForAlwaysOnVpnTests(); installAppAsUser(VPN_APP_APK, userId); try { @@ -303,6 +307,7 @@ public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest { @RequiresDevice @Test public void testAlwaysOnVpnUnsupportedPackage() throws Exception { + assumeIsNotWatch(); int userId = getUserIdForAlwaysOnVpnTests(); try { // Target SDK = 23: unsupported @@ -328,6 +333,7 @@ public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest { @RequiresDevice @Test public void testAlwaysOnVpnUnsupportedPackageReplaced() throws Exception { + assumeIsNotWatch(); int userId = getUserIdForAlwaysOnVpnTests(); try { // Target SDK = 24: supported @@ -350,6 +356,7 @@ public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest { @RequiresDevice @Test public void testAlwaysOnVpnPackageLogged() throws Exception { + assumeIsNotWatch(); int userId = getUserIdForAlwaysOnVpnTests(); // Will be uninstalled in tearDown(). installAppAsUser(VPN_APP_APK, userId); @@ -598,7 +605,7 @@ public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest { @Test public void testSetMeteredDataDisabledPackages() throws Exception { assumeHasWifiFeature(); - assumeFalse("is watch", hasDeviceFeature("android.hardware.type.watch")); + assumeIsNotWatch(); installAppAsUser(METERED_DATA_APP_APK, mUserId); @@ -1567,4 +1574,8 @@ public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest { getDevice().executeShellCommand( restricted ? RESTRICT_BACKGROUND_ON_CMD : RESTRICT_BACKGROUND_OFF_CMD); } + + private void assumeIsNotWatch() throws Exception { + assumeFalse("is watch", hasDeviceFeature("android.hardware.type.watch")); + } } diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java deleted file mode 100644 index 2decdbba8a5..00000000000 --- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java +++ /dev/null @@ -1,273 +0,0 @@ -package com.android.cts.devicepolicy; - -import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.LargeTest; - -import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures; -import com.android.tradefed.device.DeviceNotAvailableException; - -import org.junit.Test; - -import java.io.FileNotFoundException; -import java.util.HashMap; -import java.util.Map; - -/** - * CTS to verify toggling quiet mode in work profile by using - * {@link android.os.UserManager#requestQuietModeEnabled(boolean, android.os.UserHandle)}. - */ -@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS}) -public final class QuietModeHostsideTest extends BaseDevicePolicyTest { - private static final String TEST_PACKAGE = "com.android.cts.launchertests"; - private static final String TEST_CLASS = ".QuietModeTest"; - private static final String PARAM_TARGET_USER = "TARGET_USER"; - private static final String PARAM_ORIGINAL_DEFAULT_LAUNCHER = "ORIGINAL_DEFAULT_LAUNCHER"; - private static final String TEST_APK = "CtsLauncherAppsTests.apk"; - - private static final String TEST_LAUNCHER_PACKAGE = "com.android.cts.launchertests.support"; - private static final String TEST_LAUNCHER_APK = "CtsLauncherAppsTestsSupport.apk"; - private static final String ENABLED_TEST_APK = "CtsCrossProfileEnabledApp.apk"; - private static final String USER_ENABLED_TEST_APK = "CtsCrossProfileUserEnabledApp.apk"; - private static final String ENABLED_NO_PERMS_TEST_APK = "CtsCrossProfileEnabledNoPermsApp.apk"; - private static final String QUIET_MODE_ENABLED_TEST_APK = "CtsModifyQuietModeEnabledApp.apk"; - private static final String NOT_ENABLED_TEST_APK = "CtsCrossProfileNotEnabledApp.apk"; - private static final String ENABLED_TEST_PACKAGE = "com.android.cts.crossprofileenabledapp"; - private static final String USER_ENABLED_TEST_PACKAGE = - "com.android.cts.crossprofileuserenabledapp"; - private static final String ENABLED_NO_PERMS_TEST_PACKAGE = - "com.android.cts.crossprofileenablednopermsapp"; - private static final String NOT_ENABLED_TEST_PACKAGE = - "com.android.cts.crossprofilenotenabledapp"; - private static final String QUIET_MODE_ENABLED_TEST_PACKAGE = - "com.android.cts.modifyquietmodeenabledapp"; - - private int mProfileId; - private String mOriginalLauncher; - - @Override - public void setUp() throws Exception { - super.setUp(); - - mOriginalLauncher = getDefaultLauncher(); - - installAppAsUser(TEST_APK, mPrimaryUserId); - installAppAsUser(TEST_LAUNCHER_APK, mPrimaryUserId); - - waitForBroadcastIdle(); - - createAndStartManagedProfile(); - installAppAsUser(TEST_APK, mProfileId); - - waitForBroadcastIdle(); - wakeupAndDismissKeyguard(); - } - - @Override - public void tearDown() throws Exception { - uninstallRequiredApps(); - getDevice().uninstallPackage(TEST_LAUNCHER_PACKAGE); - - super.tearDown(); - } - - @LargeTest - @Test - public void testQuietMode_defaultForegroundLauncher() throws Exception { - assumeHasSecureLockScreenFeature(); - - // Add a lockscreen to test the case that profile with unified challenge can still - // be turned on without asking the user to enter the lockscreen password. - changeUserCredential(/* newCredential= */ TEST_PASSWORD, /* oldCredential= */ null, - mPrimaryUserId); - try { - runDeviceTestsAsUser( - TEST_PACKAGE, - TEST_CLASS, - "testTryEnableQuietMode_defaultForegroundLauncher", - mPrimaryUserId, - createParams(mProfileId)); - } finally { - changeUserCredential(/* newCredential= */ null, /* oldCredential= */ TEST_PASSWORD, - mPrimaryUserId); - } - } - - @LargeTest - @Test - public void testQuietMode_notForegroundLauncher() throws Exception { - runDeviceTestsAsUser( - TEST_PACKAGE, - TEST_CLASS, - "testTryEnableQuietMode_notForegroundLauncher", - mPrimaryUserId, - createParams(mProfileId)); - } - - @LargeTest - @Test - public void testQuietMode_notDefaultLauncher() throws Exception { - runDeviceTestsAsUser( - TEST_PACKAGE, - TEST_CLASS, - "testTryEnableQuietMode_notDefaultLauncher", - mPrimaryUserId, - createParams(mProfileId)); - } - - @LargeTest - @Test - public void testBroadcastManagedProfileAvailable_withoutCrossProfileAppsOp() throws Exception { - checkBroadcastManagedProfileAvailable(/* withCrossProfileAppOps= */ false); - } - - - @LargeTest - @Test - public void testBroadcastManagedProfileAvailable_withCrossProfileAppsOp() throws Exception { - checkBroadcastManagedProfileAvailable(/* withCrossProfileAppOps= */ true); - } - - private void checkBroadcastManagedProfileAvailable(boolean withCrossProfileAppOps) - throws Exception { - installCrossProfileApps(); - if (withCrossProfileAppOps) { - enableCrossProfileAppsOp(); - } - clearLogcat(); - runDeviceTestsAsUser( - TEST_PACKAGE, - TEST_CLASS, - "testTryEnableQuietMode", - mPrimaryUserId, - createParams(mProfileId)); - // In case of a necessary log is not captured - // cause of too many logs while waiting idle broadcast, capture log previously. - // This log will be concatenated. - String log = getDevice().executeAdbCommand("logcat", "-d"); - waitForBroadcastIdle(); - verifyBroadcastSent("android.intent.action.MANAGED_PROFILE_UNAVAILABLE", - /* needPermissions= */ !withCrossProfileAppOps, log); - - clearLogcat(); - runDeviceTestsAsUser( - TEST_PACKAGE, - TEST_CLASS, - "testTryDisableQuietMode", - mPrimaryUserId, - createParams(mProfileId)); - log = getDevice().executeAdbCommand("logcat", "-d"); - waitForBroadcastIdle(); - verifyBroadcastSent("android.intent.action.MANAGED_PROFILE_AVAILABLE", - /* needPermissions= */ !withCrossProfileAppOps, log); - - clearLogcat(); - removeUser(mProfileId); - log = getDevice().executeAdbCommand("logcat", "-d"); - waitForBroadcastIdle(); - verifyBroadcastSent("android.intent.action.MANAGED_PROFILE_REMOVED", - /* needPermissions= */ false, log); - } - - private void clearLogcat() throws DeviceNotAvailableException { - getDevice().executeAdbCommand("logcat", "-c"); - getDevice().executeAdbCommand("logcat", "-G", "16M"); - } - - private void verifyBroadcastSent(String actionName, boolean needPermissions, String prevLog) - throws DeviceNotAvailableException { - String result = getDevice().executeAdbCommand("logcat", "-d"); - result = prevLog + result; - assertThat(result).contains( - buildReceivedBroadcastRegex(actionName, "CrossProfileEnabledAppReceiver")); - assertThat(result).contains( - buildReceivedBroadcastRegex(actionName, "CrossProfileUserEnabledAppReceiver")); - String noPermsString = buildReceivedBroadcastRegex(actionName, - "CrossProfileEnabledNoPermsAppReceiver"); - if (needPermissions) { - assertThat(result).doesNotContain(noPermsString); - } else { - assertThat(result).contains(noPermsString); - } - assertThat(result).doesNotContain( - buildReceivedBroadcastRegex(actionName, - "CrossProfileNotEnabledAppReceiver")); - assertThat(result).contains( - buildReceivedBroadcastRegex(actionName, "ModifyQuietModeEnabledAppReceiver")); - } - - private String buildReceivedBroadcastRegex(String actionName, String className) { - return String.format("%s: onReceive(%s)", className, actionName); - } - - @LargeTest - @Test - public void testQuietMode_noCredentialRequest() throws Exception { - assumeHasSecureLockScreenFeature(); - - // Set a separate work challenge so turning on the profile requires entering the - // separate challenge. - changeUserCredential(/* newCredential= */ TEST_PASSWORD, /* oldCredential= */ null, - mProfileId); - runDeviceTestsAsUser( - TEST_PACKAGE, - TEST_CLASS, - "testTryEnableQuietMode_noCredentialRequest", - mPrimaryUserId, - createParams(mProfileId)); - } - - private void createAndStartManagedProfile() throws Exception { - mProfileId = createManagedProfile(mPrimaryUserId); - switchUser(mPrimaryUserId); - startUser(mProfileId); - } - - private void uninstallRequiredApps() - throws DeviceNotAvailableException { - getDevice().uninstallPackage(TEST_PACKAGE); - getDevice().uninstallPackage(ENABLED_TEST_PACKAGE); - getDevice().uninstallPackage(USER_ENABLED_TEST_PACKAGE); - getDevice().uninstallPackage(ENABLED_NO_PERMS_TEST_PACKAGE); - getDevice().uninstallPackage(NOT_ENABLED_TEST_PACKAGE); - getDevice().uninstallPackage(QUIET_MODE_ENABLED_TEST_PACKAGE); - } - - private void installCrossProfileApps() - throws FileNotFoundException, DeviceNotAvailableException { - installCrossProfileApp(ENABLED_TEST_APK, /* grantPermissions= */ true); - installCrossProfileApp(USER_ENABLED_TEST_APK, /* grantPermissions= */ true); - installCrossProfileApp(NOT_ENABLED_TEST_APK, /* grantPermissions= */ true); - installCrossProfileApp(ENABLED_NO_PERMS_TEST_APK, /* grantPermissions= */ false); - installCrossProfileApp(QUIET_MODE_ENABLED_TEST_APK, /* grantPermissions= */ true); - } - - private void enableCrossProfileAppsOp() throws DeviceNotAvailableException { - enableCrossProfileAppsOp(ENABLED_NO_PERMS_TEST_PACKAGE, mPrimaryUserId); - } - - private void installCrossProfileApp(String apkName, boolean grantPermissions) - throws FileNotFoundException, DeviceNotAvailableException { - installAppAsUser(apkName, grantPermissions, mPrimaryUserId); - installAppAsUser(apkName, grantPermissions, mProfileId); - } - - private void enableCrossProfileAppsOp(String packageName, int userId) - throws DeviceNotAvailableException { - getDevice().executeShellCommand( - String.format("appops set --user %s %s android:interact_across_profiles 0", - userId, packageName)); - assertThat(getDevice().executeShellCommand( - String.format("appops get --user %s %s android:interact_across_profiles", - userId, packageName))).contains("INTERACT_ACROSS_PROFILES: allow"); - } - - private Map<String, String> createParams(int targetUserId) throws Exception { - Map<String, String> params = new HashMap<>(); - params.put(PARAM_TARGET_USER, Integer.toString(getUserSerialNumber(targetUserId))); - params.put(PARAM_ORIGINAL_DEFAULT_LAUNCHER, mOriginalLauncher); - return params; - } -} diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java index 94f43fca212..cb320557f2c 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java @@ -147,6 +147,11 @@ public class BaseHdmiCecCtsTest extends BaseHostJUnit4Test { testPointer, deviceType); } + + /** This rule will skip the test if the DUT is an emulator. */ + public static TestRule requiresPhysicalDevice(BaseHostJUnit4Test testPointer) { + return RequiredDeviceTypeRule.requiredPhysicalDevice(testPointer); + } } /** @deprecated not used anymore **/ diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java index 0ff99dd9a92..1d5d2af1805 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java @@ -144,9 +144,10 @@ public final class HdmiCecConstants { public static final String HDMI_CEC_FEATURE = "feature:android.hardware.hdmi.cec"; public static final String LEANBACK_FEATURE = "feature:android.software.leanback"; - // CEC Device property list + // Device property list used in CTS public static final String HDMI_DEVICE_TYPE_PROPERTY = "ro.hdmi.device_type"; public static final String PROPERTY_ARC_SUPPORT = "persist.sys.hdmi.property_arc_support"; + public static final String PROPERTY_BUILD_FINGERPRINT = "ro.system.build.fingerprint"; /* * The default name of local directory into which the port to device mapping files are stored. diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredDeviceTypeRule.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredDeviceTypeRule.java index 935a07d0cd0..20c70d63abd 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredDeviceTypeRule.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredDeviceTypeRule.java @@ -111,5 +111,34 @@ public class RequiredDeviceTypeRule implements TestRule { } }; } + + /** + * Rule to check that the DUT is not an emulator. + * @param test The test using this rule. + * @return The rule to be used in the test. + */ + public static RequiredDeviceTypeRule requiredPhysicalDevice(final BaseHostJUnit4Test test) { + return new RequiredDeviceTypeRule() { + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + ITestDevice testDevice = test.getDevice(); + String buildFingerPrint = RequiredPropertyRule.getDevicePropertyValue(test, + HdmiCecConstants.PROPERTY_BUILD_FINGERPRINT); + // Currently only Cuttlefish is supported in automation testing. We will add + // more targets in the future if necessary. + assumeFalse( + "Invalid device " + testDevice.getSerialNumber() + " for " + + "running HDMI Control CTS. Physical device required, " + + "emulator found instead.", + buildFingerPrint.contains("cf_x86_")); + base.evaluate(); + } + }; + } + }; + } } diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java index 8c12d5469d9..0559a6523a8 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java @@ -48,6 +48,7 @@ public final class HdmiCecAudioReturnChannelControlTest extends BaseHdmiCecCtsTe public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java index b36c673ad3a..c4c44aeee92 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java @@ -61,6 +61,7 @@ public final class HdmiCecInvalidMessagesTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java index fd3ed3d1784..f6b294fe27a 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java @@ -48,6 +48,7 @@ public final class HdmiCecLogicalAddressTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java index c73506767d9..1ef9bedfab6 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java @@ -43,6 +43,7 @@ public final class HdmiCecRemoteControlPassThroughTest extends BaseHdmiCecCtsTes public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java index 3acab1c11be..8869a126f88 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java @@ -56,6 +56,7 @@ public final class HdmiCecSystemAudioModeTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecDeviceTypeTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecDeviceTypeTest.java index a38e3b2789e..4918d29778f 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecDeviceTypeTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecDeviceTypeTest.java @@ -46,7 +46,9 @@ import java.util.List; public final class HdmiCecDeviceTypeTest extends BaseHostJUnit4Test { @Rule - public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresLeanback(this)); + public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) + .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)); /** @deprecated not used anymore **/ diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java index 8cc368b533f..3c38e5eef14 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java @@ -43,6 +43,7 @@ public final class HdmiCecFeatureAbortTest extends BaseHdmiCecCtsTest { RuleChain .outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); /** diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecGeneralProtocolTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecGeneralProtocolTest.java index fd7874d2cca..298a0ad3ce2 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecGeneralProtocolTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecGeneralProtocolTest.java @@ -41,6 +41,7 @@ public final class HdmiCecGeneralProtocolTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); /** diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java index 6d3db56ed45..711a419c450 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java @@ -36,9 +36,6 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.TimeUnit; /** HDMI CEC test to verify that device ignores invalid messages (Section 12) */ @@ -68,6 +65,7 @@ public final class HdmiCecInvalidMessagesTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); @Before diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecLogicalAddressTest.java index 1c3e85a2500..c6864009ab5 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecLogicalAddressTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecLogicalAddressTest.java @@ -21,16 +21,15 @@ import static com.google.common.truth.Truth.assertThat; import android.hdmicec.cts.BaseHdmiCecCtsTest; import android.hdmicec.cts.CecMessage; import android.hdmicec.cts.CecOperand; -import android.hdmicec.cts.HdmiCecConstants; import android.hdmicec.cts.LogicalAddress; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import org.junit.Rule; +import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.runner.RunWith; -import org.junit.Test; /** HDMI CEC test to verify physical address after device reboot (Section 10.2.3) */ @RunWith(DeviceJUnit4ClassRunner.class) @@ -43,6 +42,7 @@ public final class HdmiCecLogicalAddressTest extends BaseHdmiCecCtsTest { RuleChain .outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); /** diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPhysicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPhysicalAddressTest.java index 9d3fd05e877..c17f7fbaed8 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPhysicalAddressTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPhysicalAddressTest.java @@ -37,6 +37,7 @@ public final class HdmiCecPhysicalAddressTest extends BaseHdmiCecCtsTest { RuleChain .outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); /** * Test 10.1.2-1 diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java index 8bc0f24519b..bb9f0c791db 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java @@ -35,6 +35,7 @@ public final class HdmiCecPollingTest extends BaseHdmiCecCtsTest { RuleChain .outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); /** diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java index bf7f9854565..faa163a1f17 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java @@ -54,6 +54,7 @@ public final class HdmiCecPowerStatusTest extends BaseHdmiCecCtsTest { RuleChain .outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); /** diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java index 7cd2e1f87ba..b60805f6400 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java @@ -46,6 +46,7 @@ public final class HdmiCecStartupTest extends BaseHdmiCecCtsTest { RuleChain .outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); /** diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java index 132c19fca2a..8c7a3f228ca 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java @@ -52,6 +52,7 @@ public final class HdmiCecSystemAudioControlTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.skipDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java index 5606f8720a6..06c6e7ca8f3 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java @@ -16,7 +16,6 @@ package android.hdmicec.cts.common; -import com.android.tradefed.util.RunUtil; import static android.hdmicec.cts.HdmiCecConstants.TIMEOUT_SAFETY_MS; import static com.google.common.truth.Truth.assertThat; @@ -29,6 +28,7 @@ import android.hdmicec.cts.LogicalAddress; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.util.RunUtil; import org.junit.Rule; import org.junit.Test; @@ -47,6 +47,7 @@ public final class HdmiCecSystemInformationTest extends BaseHdmiCecCtsTest { RuleChain .outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); /** diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java index e8b1d8d8d75..d300f39053d 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java @@ -55,6 +55,7 @@ public final class HdmiCecSystemStandbyTest extends BaseHdmiCecCtsTest { RuleChain .outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); @Before diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java index d44f44ccaaa..05943dc4936 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java @@ -63,6 +63,7 @@ public final class HdmiCecVendorCommandsTest extends BaseHdmiCecCtsTest { RuleChain .outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(hdmiCecClient); /** diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java index 949a3d5d50b..6c0092eee2d 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java @@ -52,6 +52,7 @@ public final class HdmiCecActiveTrackingTest extends BaseHdmiCecCtsTest { RuleChain .outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) .around(hdmiCecClient); diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecAvbToTvTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecAvbToTvTest.java index 523dee62fb3..3772dd059f3 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecAvbToTvTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecAvbToTvTest.java @@ -51,6 +51,7 @@ public class HdmiCecAvbToTvTest extends BaseHdmiCecAbsoluteVolumeBehaviorTest { public RuleChain ruleChain = RuleChain.outerRule(BaseHdmiCecCtsTest.CecRules.requiresCec(this)) .around(BaseHdmiCecCtsTest.CecRules.requiresLeanback(this)) + .around(BaseHdmiCecCtsTest.CecRules.requiresPhysicalDevice(this)) .around( BaseHdmiCecCtsTest.CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java index 459f2604d8f..f2e2661c621 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java @@ -45,6 +45,7 @@ public final class HdmiCecDeviceOsdNameTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceSelectForPlaybackTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceSelectForPlaybackTest.java index 3bc9ec2211c..d14e1794d02 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceSelectForPlaybackTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceSelectForPlaybackTest.java @@ -55,6 +55,7 @@ public class HdmiCecDeviceSelectForPlaybackTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecGeneralProtocolTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecGeneralProtocolTest.java index 444b0bdbd6e..6a92d5254b1 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecGeneralProtocolTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecGeneralProtocolTest.java @@ -37,6 +37,7 @@ public final class HdmiCecGeneralProtocolTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java index 7a8e3d418c2..29a7c30fd89 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java @@ -56,6 +56,7 @@ public final class HdmiCecOneTouchPlayTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java index 5b361be5876..1460768f1e3 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java @@ -67,6 +67,7 @@ public final class HdmiCecPowerStatusTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java index dad1800de87..4a64e5c63a0 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java @@ -52,6 +52,7 @@ public final class HdmiCecRemoteControlPassThroughTest extends BaseHdmiCecCtsTes public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java index fc5ea0cad84..7ad69634cfd 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java @@ -49,6 +49,7 @@ public final class HdmiCecRoutingControlTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSoundbarModeTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSoundbarModeTest.java index 3469beb5bd3..5e4401d5f9b 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSoundbarModeTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSoundbarModeTest.java @@ -46,6 +46,7 @@ public class HdmiCecSoundbarModeTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresArcSupport(this, true)) .around(CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStandbyTest.java index d9f0b95d1b5..f75218cf1b8 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStandbyTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStandbyTest.java @@ -25,10 +25,10 @@ import android.hdmicec.cts.LogicalAddress; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import org.junit.runner.RunWith; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; import java.util.concurrent.TimeUnit; @@ -40,6 +40,7 @@ public final class HdmiCecStandbyTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java index f0fbd1e5b98..701b6d74647 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java @@ -44,6 +44,7 @@ public final class HdmiCecSystemAudioControlTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java index bd56af97e87..3e7ca551e24 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java @@ -33,8 +33,6 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.runner.RunWith; -import java.util.concurrent.TimeUnit; - /** HDMI CEC system information tests (Section 11.2.6) */ @RunWith(DeviceJUnit4ClassRunner.class) public final class HdmiCecSystemInformationTest extends BaseHdmiCecCtsTest { @@ -43,6 +41,7 @@ public final class HdmiCecSystemInformationTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java index 14700a13493..89056594eb0 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java @@ -48,6 +48,7 @@ public final class HdmiCecTvPowerToggleTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecWakeupTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecWakeupTest.java index b9b21eb3607..6b05e181c9b 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecWakeupTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecWakeupTest.java @@ -40,6 +40,7 @@ public final class HdmiCecWakeupTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java index 470937486fd..fcdac7c3b56 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java @@ -36,12 +36,12 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; +import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; -import java.io.IOException; import java.util.ArrayList; -import java.util.concurrent.TimeUnit; import java.util.List; +import java.util.concurrent.TimeUnit; /* Sets up the CEC tests by discovering which port the CEC adapter connected to */ public class CecPortDiscoverer extends BaseTargetPreparer { @@ -68,7 +68,8 @@ public class CecPortDiscoverer extends BaseTargetPreparer { throws TargetSetupError, DeviceNotAvailableException { ITestDevice device = testInfo.getDevice(); if (!device.hasFeature("feature:android.hardware.hdmi.cec") - || !device.hasFeature("feature:android.software.leanback")) { + || !device.hasFeature("feature:android.software.leanback") + || isTvEmulator(device)) { // We are testing non-HDMI devices, so don't check for adapter availability return; } @@ -80,6 +81,18 @@ public class CecPortDiscoverer extends BaseTargetPreparer { } } + /** + * Check if the DUT is an emulator. + * @param device The DUT. + * @return true If the DUT is an emulator. + * @throws DeviceNotAvailableException + */ + public static boolean isTvEmulator(ITestDevice device) throws DeviceNotAvailableException { + return !device.executeShellCommand("getprop " + HdmiCecConstants.PROPERTY_BUILD_FINGERPRINT + + " | grep \"cf_x86\"") + .isEmpty(); + } + /** {@inheritDoc} */ @Override public void tearDown(TestInformation testInfo, Throwable e) { diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAbsoluteVolumeControlFollowerTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAbsoluteVolumeControlFollowerTest.java index 77dbcb27ed3..c958ec5d684 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAbsoluteVolumeControlFollowerTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAbsoluteVolumeControlFollowerTest.java @@ -46,6 +46,7 @@ public class HdmiCecAbsoluteVolumeControlFollowerTest extends BaseHdmiCecCtsTest public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV)) .around(hdmiCecClient); diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAudioReturnChannelControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAudioReturnChannelControlTest.java index a368bdf4ea1..0511e99769e 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAudioReturnChannelControlTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAudioReturnChannelControlTest.java @@ -47,6 +47,7 @@ public final class HdmiCecAudioReturnChannelControlTest extends BaseHdmiCecCtsTe public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV)) .around(hdmiCecClient); diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAvbToAudioSystemTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAvbToAudioSystemTest.java index 6cba5d1c395..b0ed919398b 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAvbToAudioSystemTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAvbToAudioSystemTest.java @@ -54,6 +54,7 @@ public class HdmiCecAvbToAudioSystemTest extends BaseHdmiCecAbsoluteVolumeBehavi public RuleChain ruleChain = RuleChain.outerRule(BaseHdmiCecCtsTest.CecRules.requiresCec(this)) .around(BaseHdmiCecCtsTest.CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around( BaseHdmiCecCtsTest.CecRules.requiresDeviceType( this, HdmiCecConstants.CEC_DEVICE_TYPE_TV)) diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecGeneralProtocolTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecGeneralProtocolTest.java index 0aa21b8ab23..1658bf4696c 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecGeneralProtocolTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecGeneralProtocolTest.java @@ -37,6 +37,7 @@ public final class HdmiCecGeneralProtocolTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV)) .around(hdmiCecClient); diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java index 605ecee84e0..c933a3c67a8 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java @@ -55,6 +55,7 @@ public final class HdmiCecRemoteControlPassThroughTest extends BaseHdmiCecCtsTes public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV)) .around(hdmiCecClient); diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java index 55723a447d1..8033459f66e 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java @@ -47,6 +47,7 @@ public final class HdmiCecRoutingControlTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV)) .around(hdmiCecClient); diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java index 54c296e8270..929be019e09 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java @@ -44,6 +44,7 @@ public final class HdmiCecSystemAudioControlTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV)) .around(hdmiCecClient); diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemInformationTest.java index 83b7c4ba767..9da2e346cd7 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemInformationTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemInformationTest.java @@ -41,6 +41,7 @@ public final class HdmiCecSystemInformationTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV)) .around(hdmiCecClient); diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java index 7ee413a9d3a..86f1a9e26e0 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java @@ -60,6 +60,7 @@ public class HdmiCecTvOneTouchPlayTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV)) .around(hdmiCecClient); diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java index 2ab3c24c6aa..9981fe67564 100644 --- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java +++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java @@ -45,6 +45,7 @@ public class HdmiCecTvStandbyTest extends BaseHdmiCecCtsTest { public RuleChain ruleChain = RuleChain.outerRule(CecRules.requiresCec(this)) .around(CecRules.requiresLeanback(this)) + .around(CecRules.requiresPhysicalDevice(this)) .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV)) .around(hdmiCecClient); diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java index 99f7e5b0581..ef1181da97a 100644 --- a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java +++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java @@ -63,8 +63,8 @@ public class PreferredActivitiesTest extends BaseDynamicMimeTest { private static final String NAV_BAR_INTERACTION_MODE_RES_NAME = "config_navBarInteractionMode"; private static final int NAV_BAR_INTERACTION_MODE_GESTURAL = 2; - private static final String BUTTON_ALWAYS_RES_ID = "android:id/button_always"; - private static final BySelector BUTTON_ALWAYS = By.res(BUTTON_ALWAYS_RES_ID); + private static final String BUTTON_ALWAYS_RES_ID = ".*:id/button_always.*"; + private static final BySelector BUTTON_ALWAYS = By.res(Pattern.compile(BUTTON_ALWAYS_RES_ID)); private static final UiSelector BUTTON_ALWAYS_UI_SELECTOR = new UiSelector().resourceId(BUTTON_ALWAYS_RES_ID); private static final BySelector RESOLVER_DIALOG = By.res(Pattern.compile(".*:id/contentPanel.*")); diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp index a919d1b1e63..3f911187a8d 100644 --- a/hostsidetests/scopedstorage/Android.bp +++ b/hostsidetests/scopedstorage/Android.bp @@ -304,6 +304,85 @@ android_test_helper_app { ], } +android_test_helper_app { + name: "CtsScopedStorageGeneralTestOnlyApp", + manifest: "ScopedStorageTestHelper/TestAppGeneralOnly.xml", + static_libs: ["cts-scopedstorage-lib"], + sdk_version: "test_current", + target_sdk_version: "33", + min_sdk_version: "29", + srcs: ["ScopedStorageTestHelper/src/**/*.java"], + // Tag as a CTS artifact + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], +} + +android_test_helper_app { + name: "CtsScopedStorageTestAppELegacy", + manifest: "ScopedStorageTestHelper/TestAppELegacy.xml", + static_libs: ["cts-scopedstorage-lib"], + sdk_version: "test_current", + target_sdk_version: "28", + min_sdk_version: "28", + srcs: ["ScopedStorageTestHelper/src/**/*.java"], + // Tag as a CTS artifact + test_suites: [ + "general-tests", + "mts-mediaprovider", + ], +} + +android_test_helper_app { + name: "CtsScopedStorageTestAppE", + manifest: "ScopedStorageTestHelper/TestAppE.xml", + static_libs: ["cts-scopedstorage-lib"], + sdk_version: "test_current", + target_sdk_version: "33", + min_sdk_version: "29", + srcs: ["ScopedStorageTestHelper/src/**/*.java"], + // Tag as a CTS artifact + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], +} + +android_test_helper_app { + name: "CtsScopedStorageTestAppE30", + manifest: "ScopedStorageTestHelper/TestAppE30.xml", + static_libs: ["cts-scopedstorage-lib"], + sdk_version: "test_current", + target_sdk_version: "30", + min_sdk_version: "29", + srcs: ["ScopedStorageTestHelper/src/**/*.java"], + // Tag as a CTS artifact + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], +} + +android_test_helper_app { + name: "CtsScopedStorageTestAppE30FileManager", + manifest: "ScopedStorageTestHelper/TestAppE30FileManager.xml", + static_libs: ["cts-scopedstorage-lib"], + sdk_version: "test_current", + target_sdk_version: "30", + min_sdk_version: "29", + srcs: ["ScopedStorageTestHelper/src/**/*.java"], + // Tag as a CTS artifact + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], +} + java_test_host { name: "CtsScopedStorageCoreHostTest", srcs: [ @@ -481,6 +560,8 @@ android_test { ":CtsScopedStorageTestAppSystemGallery30BypassDB", ":CtsTestAppWithQueryAllPackagesPermission", ":CtsTestAppWithQueriesTag", + ":CtsScopedStorageTestAppE30", + ":CtsScopedStorageTestAppE30FileManager", ], data: [ ":CtsScopedStorageTestAppFileManager", @@ -489,6 +570,139 @@ android_test { ":CtsScopedStorageTestAppDLegacy", ":CtsTestAppWithQueryAllPackagesPermission", ":CtsTestAppWithQueriesTag", + ":CtsScopedStorageTestAppE30", + ":CtsScopedStorageTestAppE30FileManager", + ":CtsScopedStorageTestAppSystemGalleryBypassDB", + ":CtsScopedStorageTestAppSystemGallery30BypassDB", + ":CtsScopedStorageTestAppFileManagerBypassDB", + ], + per_testcase_directory: true, +} + +android_test { + name: "CtsScopedStorageBypassDatabaseOperationsTest", + manifest: "bypassdatabase/AndroidManifest.xml", + test_config: "bypassdatabase/AndroidTest.xml", + srcs: ["bypassdatabase/**/*.java"], + static_libs: [ + "truth", + "cts-scopedstorage-lib", + "androidx.test.uiautomator_uiautomator", + "modules-utils-build_system", + ], + compile_multilib: "both", + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], + sdk_version: "test_current", + target_sdk_version: "33", + min_sdk_version: "29", + libs: [ + "android.test.base", + "android.test.mock", + "android.test.runner", + ], + java_resources: [ + ":CtsScopedStorageTestAppA", + ":CtsScopedStorageTestAppSystemGalleryBypassDB", + ":CtsScopedStorageTestAppE30", + ":CtsScopedStorageTestAppE30FileManager", + ":CtsScopedStorageTestAppSystemGallery30BypassDB", + ":CtsScopedStorageTestAppFileManager", + ":CtsScopedStorageTestAppFileManagerBypassDB", + ], + data: [ + ":CtsScopedStorageTestAppA", + ":CtsScopedStorageTestAppSystemGalleryBypassDB", + ":CtsScopedStorageTestAppE30", + ":CtsScopedStorageTestAppE30FileManager", + ":CtsScopedStorageTestAppSystemGallery30BypassDB", + ":CtsScopedStorageTestAppFileManager", + ":CtsScopedStorageTestAppFileManagerBypassDB", + ], + per_testcase_directory: true, +} + +android_test { + name: "CtsScopedStorageGeneralTest", + manifest: "general/AndroidManifest.xml", + test_config: "general/AndroidTest.xml", + srcs: ["general/**/*.java"], + static_libs: [ + "truth", + "cts-scopedstorage-lib", + "androidx.test.uiautomator_uiautomator", + "modules-utils-build_system", + ], + compile_multilib: "both", + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], + sdk_version: "test_current", + target_sdk_version: "33", + min_sdk_version: "29", + libs: [ + "android.test.base", + "android.test.mock", + "android.test.runner", + ], + java_resources: [ + ":CtsScopedStorageTestAppA", + ":CtsScopedStorageTestAppB", + ":CtsScopedStorageTestAppFileManager", + ":CtsScopedStorageTestAppDLegacy", + ":CtsScopedStorageGeneralTestOnlyApp", + ":CtsScopedStorageTestAppE", + ":CtsScopedStorageTestAppELegacy", + ], + data: [ + ":CtsScopedStorageTestAppA", + ":CtsScopedStorageTestAppB", + ":CtsScopedStorageTestAppFileManager", + ":CtsScopedStorageTestAppDLegacy", + ":CtsScopedStorageGeneralTestOnlyApp", + ":CtsScopedStorageTestAppE", + ":CtsScopedStorageTestAppELegacy", + ], + per_testcase_directory: true, +} + +android_test { + name: "CtsScopedStorageRedactUriTest", + manifest: "redacturi/AndroidManifest.xml", + test_config: "redacturi/AndroidTest.xml", + srcs: ["redacturi/**/*.java"], + static_libs: [ + "truth", + "cts-scopedstorage-lib", + "androidx.test.uiautomator_uiautomator", + "modules-utils-build_system", + ], + compile_multilib: "both", + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], + sdk_version: "test_current", + target_sdk_version: "33", + min_sdk_version: "29", + libs: [ + "android.test.base", + "android.test.mock", + "android.test.runner", + ], + java_resources: [ + ":CtsScopedStorageTestAppB", + ":CtsScopedStorageTestAppE", + ], + data: [ + ":CtsScopedStorageTestAppB", + ":CtsScopedStorageTestAppE", ], per_testcase_directory: true, } diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppE.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppE.xml new file mode 100644 index 00000000000..12e4ae70137 --- /dev/null +++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppE.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.scopedstorage.cts.testapp.E" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="29" /> + + <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> + <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> + <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> + + <application android:label="TestAppE"> + <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="android.scopedstorage.cts.testapp.E" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/file_paths" /> + </provider> + </application> +</manifest> diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppE30.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppE30.xml new file mode 100644 index 00000000000..0b30e3fa48e --- /dev/null +++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppE30.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.scopedstorage.cts.testapp.E30" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="30" /> + + <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> + <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> + <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> + + <application android:label="TestAppE30"> + <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="android.scopedstorage.cts.testapp.E30" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/file_paths" /> + </provider> + </application> +</manifest> diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppE30FileManager.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppE30FileManager.xml new file mode 100644 index 00000000000..30b38d5240f --- /dev/null +++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppE30FileManager.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.scopedstorage.cts.testapp.E30.filemanager" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="30" /> + + <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> + <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> + <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> + + <application android:label="TestAppE30FileManager"> + <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="android.scopedstorage.cts.testapp.E30.filemanager" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/file_paths" /> + </provider> + </application> +</manifest> diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppELegacy.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppELegacy.xml new file mode 100644 index 00000000000..88fc52a4aea --- /dev/null +++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppELegacy.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.scopedstorage.cts.testapp.E.legacy" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" /> + + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + + <application android:label="TestAppELegacy"> + <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="android.scopedstorage.cts.testapp.E.legacy" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/file_paths" /> + </provider> + </application> +</manifest> diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppGeneralOnly.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppGeneralOnly.xml new file mode 100644 index 00000000000..f895b653ae2 --- /dev/null +++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppGeneralOnly.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.scopedstorage.cts.testapp.general.only" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="29" /> + + <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> + <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> + <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> + + <application android:label="TestAppGeneralOnly"> + <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="android.scopedstorage.cts.testapp.general.only" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/file_paths" /> + </provider> + </application> +</manifest> diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGallery30BypassDB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGallery30BypassDB.xml index b56e0cd61bc..52050bad0c4 100644 --- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGallery30BypassDB.xml +++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGallery30BypassDB.xml @@ -16,18 +16,20 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.scopedstorage.cts.testapp.SystemGalleryBypassDB" + package="android.scopedstorage.cts.testapp.SystemGallery30BypassDB" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="30" /> + <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> + <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> + <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> - <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> - <application android:label="TestAppSystemGalleryBypassDB" android:requestRawExternalStorageAccess="true"> + <application android:label="TestAppSystemGallery30BypassDB" android:requestRawExternalStorageAccess="true"> <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" > <intent-filter> <action android:name="android.intent.action.MAIN" /> @@ -38,7 +40,7 @@ <provider android:name="androidx.core.content.FileProvider" - android:authorities="android.scopedstorage.cts.testapp.SystemGalleryBypassDB" + android:authorities="android.scopedstorage.cts.testapp.SystemGallery30BypassDB" android:exported="false" android:grantUriPermissions="true"> <meta-data diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml index ed4cd60ca40..c2a63aa2922 100644 --- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml +++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml @@ -28,7 +28,6 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> - <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <application android:label="TestAppSystemGalleryBypassDB" android:requestRawExternalStorageAccess="true"> <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" > diff --git a/hostsidetests/scopedstorage/TEST_MAPPING b/hostsidetests/scopedstorage/TEST_MAPPING index 60a142fd107..ad086654c3a 100644 --- a/hostsidetests/scopedstorage/TEST_MAPPING +++ b/hostsidetests/scopedstorage/TEST_MAPPING @@ -8,6 +8,15 @@ }, { "name": "CtsScopedStorageDeviceOnlyTest[com.google.android.mediaprovider.apex]" + }, + { + "name": "CtsScopedStorageBypassDatabaseOperationsTest[com.google.android.mediaprovider.apex]" + }, + { + "name": "CtsScopedStorageGeneralTest[com.google.android.mediaprovider.apex]" + }, + { + "name": "CtsScopedStorageRedactUriTest[com.google.android.mediaprovider.apex]" } ], "presubmit": [ @@ -19,6 +28,15 @@ }, { "name": "CtsScopedStorageDeviceOnlyTest" + }, + { + "name": "CtsScopedStorageBypassDatabaseOperationsTest" + }, + { + "name": "CtsScopedStorageGeneralTest" + }, + { + "name": "CtsScopedStorageRedactUriTest" } ], "presubmit-large": [ diff --git a/hostsidetests/scopedstorage/bypassdatabase/AndroidManifest.xml b/hostsidetests/scopedstorage/bypassdatabase/AndroidManifest.xml new file mode 100644 index 00000000000..8dd4d6dbd2b --- /dev/null +++ b/hostsidetests/scopedstorage/bypassdatabase/AndroidManifest.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.scopedstorage.cts.bypassdatabase"> + + <uses-sdk android:minSdkVersion="29" /> + + <queries> + <package android:name="android.scopedstorage.cts.testapp.A.withres" /> + <package android:name="android.scopedstorage.cts.testapp.SystemGalleryBypassDB" /> + <package android:name="android.scopedstorage.cts.testapp.SystemGallery30BypassDB" /> + <package android:name="android.scopedstorage.cts.testapp.E30" /> + <package android:name="android.scopedstorage.cts.testapp.E30.filemanager" /> + <package android:name="android.scopedstorage.cts.testapp.filemanager" /> + <package android:name="android.scopedstorage.cts.testapp.filemanagerbypassdb" /> + </queries> + + <application android:label="Scoped Storage Device Bypass Database Operations Tests"> + <uses-library android:name="android.test.runner" /> + <activity android:name="android.scopedstorage.cts.lib.GetResultActivity"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.scopedstorage.cts.bypassdatabase" + android:label="Device-only scoped storage bypass database operations tests" /> + +</manifest> diff --git a/hostsidetests/scopedstorage/bypassdatabase/AndroidTest.xml b/hostsidetests/scopedstorage/bypassdatabase/AndroidTest.xml new file mode 100644 index 00000000000..064b67a1687 --- /dev/null +++ b/hostsidetests/scopedstorage/bypassdatabase/AndroidTest.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Runs device-only tests for scoped storage bypass database"> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="CtsScopedStorageBypassDatabaseOperationsTest.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppE30.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppE30FileManager.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppFileManagerBypassDB.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppSystemGalleryBypassDB.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppSystemGallery30BypassDB.apk" /> + </target_preparer> + + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + + <option name="config-descriptor:metadata" key="component" value="framework" /> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" /> + <option name="test-suite-tag" value="cts" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.scopedstorage.cts.bypassdatabase" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> + + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.mediaprovider" /> + </object> +</configuration> diff --git a/hostsidetests/scopedstorage/bypassdatabase/src/android/scopedstorage/cts/bypassdatabase/BypassDatabaseOperationsTest.java b/hostsidetests/scopedstorage/bypassdatabase/src/android/scopedstorage/cts/bypassdatabase/BypassDatabaseOperationsTest.java new file mode 100644 index 00000000000..a7c9344ddec --- /dev/null +++ b/hostsidetests/scopedstorage/bypassdatabase/src/android/scopedstorage/cts/bypassdatabase/BypassDatabaseOperationsTest.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.scopedstorage.cts.bypassdatabase; + +import static android.app.AppOpsManager.permissionToOp; +import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid; +import static android.scopedstorage.cts.lib.TestUtils.createFileAs; +import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs; +import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow; +import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid; +import static android.scopedstorage.cts.lib.TestUtils.getContentResolver; +import static android.scopedstorage.cts.lib.TestUtils.getDcimDir; +import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir; +import static android.scopedstorage.cts.lib.TestUtils.renameFileAs; + +import static androidx.test.InstrumentationRegistry.getContext; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import android.Manifest; +import android.app.AppOpsManager; +import android.provider.MediaStore; +import android.scopedstorage.cts.lib.ScopedStorageBaseDeviceTest; +import android.scopedstorage.cts.lib.TestUtils; +import android.util.Log; + +import androidx.test.filters.SdkSuppress; + +import com.android.cts.install.lib.TestApp; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.io.File; + +/** + * Device-side test suite to verify file path operations optionally bypassing database operations. + */ +@RunWith(Parameterized.class) +public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest { + static final String TAG = "BypassDatabaseOperationsTest"; + // An app with READ_EXTERNAL_STORAGE permission. Targets current SDK and is preinstalled + private static final TestApp APP_SYSTEM_GALLERY_DEFAULT = new TestApp("TestAppA", + "android.scopedstorage.cts.testapp.A.withres", 1, false, + "CtsScopedStorageTestAppA.apk"); + // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets current SDK and has + // requestRawExternalStorageAccess=true + private static final TestApp APP_SYSTEM_GALLERY_BYPASS_DB = new TestApp( + "TestAppSystemGalleryBypassDB", + "android.scopedstorage.cts.testapp.SystemGalleryBypassDB", 1, false, + "CtsScopedStorageTestAppSystemGalleryBypassDB.apk"); + // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets targetSDK=30. + private static final TestApp APP_SYSTEM_GALLERY_30 = new TestApp("TestAppE30", + "android.scopedstorage.cts.testapp.E30", 1, false, + "CtsScopedStorageTestAppE30.apk"); + // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets targetSDK=30 and has + // requestRawExternalStorageAccess=true + private static final TestApp APP_SYSTEM_GALLERY_30_BYPASS_DB = new TestApp( + "TestAppSystemGallery30BypassDB", + "android.scopedstorage.cts.testapp.SystemGallery30BypassDB", 1, false, + "CtsScopedStorageTestAppSystemGallery30BypassDB.apk"); + // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission. + // Targets current SDK and preinstalled + private static final TestApp APP_FM_DEFAULT = new TestApp( + "TestAppFileManager", "android.scopedstorage.cts.testapp.filemanager", 1, false, + "CtsScopedStorageTestAppFileManager.apk"); + // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission. + // Targets current SDK and has requestRawExternalStorageAccess=true + private static final TestApp APP_FM_BYPASS_DATABASE_OPS = new TestApp( + "TestAppFileManagerBypassDB", "android.scopedstorage.cts.testapp.filemanagerbypassdb", + 1, false, "CtsScopedStorageTestAppFileManagerBypassDB.apk"); + // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission and targets targetSDK=30 + private static final TestApp APP_FM_TARGETS_30 = new TestApp("TestAppE30FileManager", + "android.scopedstorage.cts.testapp.E30.filemanager", 1, false, + "CtsScopedStorageTestAppE30FileManager.apk"); + + private static final String OPSTR_MANAGE_EXTERNAL_STORAGE = + permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE); + private static final String[] SYSTEM_GALLERY_APPOPS = { + AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO}; + + /** + * To help avoid flaky tests, give ourselves a unique nonce to be used for + * all filesystem paths, so that we don't risk conflicting with previous + * test runs. + */ + static final String NONCE = String.valueOf(System.nanoTime()); + + static final String IMAGE_FILE_NAME = "BypassDatabaseOperations_file_" + NONCE + ".jpg"; + + @BeforeClass + public static void setupApps() throws Exception { + // File manager needs to be explicitly granted MES app op. + final int fmUid = + getContext().getPackageManager().getPackageUid( + APP_FM_DEFAULT.getPackageName(), 0); + allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE); + } + + @Parameter(0) + public String mVolumeName; + + @Parameters(name = "volume={0}") + public static Iterable<? extends Object> data() { + return ScopedStorageBaseDeviceTest.getTestParameters(); + } + + @Before + public void setupExternalStorage() throws Exception { + super.setupExternalStorage(mVolumeName); + Log.i(TAG, "Using volume : " + mVolumeName); + } + + + /** + * Test that app with MANAGE_EXTERNAL_STORAGE permission and targeting + * targetSDK=31 or higher will not bypass database operations by default. + */ + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testManageExternalStorage_DoesntBypassDatabase_afterS() throws Exception { + testAppDoesntBypassDatabaseOps(APP_FM_DEFAULT); + } + + /** + * Test that app with MANAGE_EXTERNAL_STORAGE permission, targeting + * targetSDK=31 or higher and with requestRawExternalStorageAccess=true + * will bypass database operations. + */ + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testManageExternalStorage_WithBypassFlag_BypassesDatabase() throws Exception { + final int fmUid = getContext().getPackageManager().getPackageUid( + APP_FM_BYPASS_DATABASE_OPS.getPackageName(), 0); + allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE); + testAppBypassesDatabaseOps(APP_FM_BYPASS_DATABASE_OPS); + } + + /** + * Test that app with MANAGE_EXTERNAL_STORAGE permission and targeting + * targetSDK=30 or lower will bypass database operations by default. + */ + @Test + public void testManageExternalStorage_targets30_BypassesDatabase() throws Exception { + final int fmUid = getContext().getPackageManager().getPackageUid( + APP_FM_TARGETS_30.getPackageName(), 0); + allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE); + testAppBypassesDatabaseOps(APP_FM_TARGETS_30); + } + + /** + * Test that app with SYSTEM_GALLERY role and targeting + * targetSDK=current or higher will not bypass database operations by default. + */ + @Test + public void testSystemGallery_DoesntBypassDatabase() throws Exception { + final int sgUid = + getContext().getPackageManager().getPackageUid( + APP_SYSTEM_GALLERY_DEFAULT.getPackageName(), 0); + try { + allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); + testAppDoesntBypassDatabaseOps(APP_SYSTEM_GALLERY_DEFAULT); + } finally { + denyAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); + } + } + + + /** + * Test that app with SYSTEM_GALLERY role, targeting + * targetSDK=current or higher and with requestOptimizedSystemGalleryAccess=true + * will bypass database operations. + */ + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testSystemGallery_WithBypassFlag_BypassesDatabase() throws Exception { + final int sgUid = getContext().getPackageManager().getPackageUid( + APP_SYSTEM_GALLERY_BYPASS_DB.getPackageName(), 0); + allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); + testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_BYPASS_DB); + } + + /** + * Test that app with SYSTEM_GALLERY role and targeting + * targetSDK=30 or higher will not bypass database operations by default. + */ + @Test + public void testSystemGallery_targets30_DoesntBypassDatabase() throws Exception { + final int sgUid = getContext().getPackageManager().getPackageUid( + APP_SYSTEM_GALLERY_30.getPackageName(), 0); + allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); + testAppDoesntBypassDatabaseOps(APP_SYSTEM_GALLERY_30); + } + + /** + * Test that app with SYSTEM_GALLERY role, targeting + * targetSDK=30 or higher and with requestOptimizedSystemGalleryAccess=true + * will bypass database operations. + */ + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testSystemGallery_targets30_WithBypassFlag_BypassesDatabase() throws Exception { + final int sgUid = getContext().getPackageManager().getPackageUid( + APP_SYSTEM_GALLERY_30_BYPASS_DB.getPackageName(), 0); + allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); + testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_30_BYPASS_DB); + } + + private void testAppDoesntBypassDatabaseOps(TestApp app) throws Exception { + final File file = new File(getDcimDir(), IMAGE_FILE_NAME); + final File renamedFile = new File(getPicturesDir(), IMAGE_FILE_NAME); + try { + assertThat(createFileAs(app, file.getAbsolutePath())).isTrue(); + // File path create() added file to database. + assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isTrue(); + + assertThat(renameFileAs(app, file, renamedFile)).isTrue(); + // File path rename() also updates the database row + assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isFalse(); + assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue(); + + assertThat(deleteFileAs(app, renamedFile.getAbsolutePath())).isTrue(); + // File path delete() removes database row. + assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isFalse(); + } finally { + if (file.exists()) { + deleteFileAsNoThrow(app, file.getAbsolutePath()); + } + if (renamedFile.exists()) { + deleteFileAsNoThrow(app, renamedFile.getAbsolutePath()); + } + } + } + + private void testAppBypassesDatabaseOps(TestApp app) throws Exception { + final File file = new File(getDcimDir(), IMAGE_FILE_NAME); + final File renamedFile = new File(getPicturesDir(), IMAGE_FILE_NAME); + try { + assertThat(createFileAs(app, file.getAbsolutePath())).isTrue(); + // File path create() didn't add the file to database. + assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isFalse(); + + // Ensure file is added to database. + assertNotNull(MediaStore.scanFile(getContentResolver(), file)); + + assertThat(renameFileAs(app, file, renamedFile)).isTrue(); + // Rename() didn't update the database row. + assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isTrue(); + assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isFalse(); + + // Ensure database is updated with renamed path + assertNull(MediaStore.scanFile(getContentResolver(), file)); + assertNotNull(MediaStore.scanFile(getContentResolver(), renamedFile)); + assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue(); + + assertThat(deleteFileAs(app, renamedFile.getAbsolutePath())).isTrue(); + // Unlink() didn't remove the database row. + assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue(); + } finally { + if (file.exists()) { + deleteFileAsNoThrow(app, file.getAbsolutePath()); + } + if (renamedFile.exists()) { + deleteFileAsNoThrow(app, renamedFile.getAbsolutePath()); + } + MediaStore.scanFile(getContentResolver(), file); + MediaStore.scanFile(getContentResolver(), renamedFile); + } + } +} diff --git a/hostsidetests/scopedstorage/device/AndroidManifest.xml b/hostsidetests/scopedstorage/device/AndroidManifest.xml index 8d397e28ff5..c9ff6a9028d 100644 --- a/hostsidetests/scopedstorage/device/AndroidManifest.xml +++ b/hostsidetests/scopedstorage/device/AndroidManifest.xml @@ -33,6 +33,9 @@ <package android:name="android.scopedstorage.cts.testapp.SystemGalleryBypassDB" /> <package android:name="android.scopedstorage.cts.testapp.withqueriestag" /> <package android:name="android.scopedstorage.cts.testapp.withqueryallpackagestag" /> + <package android:name="android.scopedstorage.cts.testapp.SystemGallery30BypassDB" /> + <package android:name="android.scopedstorage.cts.testapp.E30" /> + <package android:name="android.scopedstorage.cts.testapp.E30.filemanager" /> </queries> <application android:label="Scoped Storage Device Tests"> diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml index 648469e519a..d26f207ee6f 100644 --- a/hostsidetests/scopedstorage/device/AndroidTest.xml +++ b/hostsidetests/scopedstorage/device/AndroidTest.xml @@ -23,6 +23,11 @@ <option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" /> <option name="test-file-name" value="CtsTestAppWithQueryAllPackagesPermission.apk" /> <option name="test-file-name" value="CtsTestAppWithQueriesTag.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppE30.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppE30FileManager.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppFileManagerBypassDB.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppSystemGalleryBypassDB.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppSystemGallery30BypassDB.apk" /> </target_preparer> <option diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java index 51f61ecd5a5..9caf9d708c2 100644 --- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java +++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java @@ -25,10 +25,7 @@ import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid; import static android.scopedstorage.cts.lib.TestUtils.getContentResolver; import static android.scopedstorage.cts.lib.TestUtils.getDcimDir; import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir; -import static android.scopedstorage.cts.lib.TestUtils.installApp; -import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions; import static android.scopedstorage.cts.lib.TestUtils.renameFileAs; -import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow; import static androidx.test.InstrumentationRegistry.getContext; @@ -40,6 +37,7 @@ import static org.junit.Assert.assertNull; import android.Manifest; import android.app.AppOpsManager; import android.provider.MediaStore; +import android.scopedstorage.cts.lib.ScopedStorageBaseDeviceTest; import android.scopedstorage.cts.lib.TestUtils; import android.util.Log; @@ -58,7 +56,9 @@ import org.junit.runners.Parameterized.Parameters; import java.io.File; /** - * Device-side test suite to verify file path operations optionally bypassing database operations. + * We are in process of splitting CtsScopedStorageDeviceOnlyTest module into multiple ones. + * This is a temporary test class, and it will be deleted + * after all the links to new modules are created. */ @RunWith(Parameterized.class) public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest { @@ -74,14 +74,14 @@ public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest { "android.scopedstorage.cts.testapp.SystemGalleryBypassDB", 1, false, "CtsScopedStorageTestAppSystemGalleryBypassDB.apk"); // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets targetSDK=30. - private static final TestApp APP_SYSTEM_GALLERY_30 = new TestApp("TestAppC", - "android.scopedstorage.cts.testapp.C", 1, false, - "CtsScopedStorageTestAppC30.apk"); + private static final TestApp APP_SYSTEM_GALLERY_30 = new TestApp("TestAppE30", + "android.scopedstorage.cts.testapp.E30", 1, false, + "CtsScopedStorageTestAppE30.apk"); // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets targetSDK=30 and has // requestRawExternalStorageAccess=true private static final TestApp APP_SYSTEM_GALLERY_30_BYPASS_DB = new TestApp( - "TestAppSystemGalleryBypassDB", - "android.scopedstorage.cts.testapp.SystemGalleryBypassDB", 1, false, + "TestAppSystemGallery30BypassDB", + "android.scopedstorage.cts.testapp.SystemGallery30BypassDB", 1, false, "CtsScopedStorageTestAppSystemGallery30BypassDB.apk"); // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission. // Targets current SDK and preinstalled @@ -94,9 +94,9 @@ public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest { "TestAppFileManagerBypassDB", "android.scopedstorage.cts.testapp.filemanagerbypassdb", 1, false, "CtsScopedStorageTestAppFileManagerBypassDB.apk"); // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission and targets targetSDK=30 - private static final TestApp APP_FM_TARGETS_30 = new TestApp("TestAppC", - "android.scopedstorage.cts.testapp.C", 1, false, - "CtsScopedStorageTestAppC30.apk"); + private static final TestApp APP_FM_TARGETS_30 = new TestApp("TestAppE30FileManager", + "android.scopedstorage.cts.testapp.E30.filemanager", 1, false, + "CtsScopedStorageTestAppE30FileManager.apk"); private static final String OPSTR_MANAGE_EXTERNAL_STORAGE = permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE); @@ -154,16 +154,10 @@ public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest { @Test @SdkSuppress(minSdkVersion = 31, codeName = "S") public void testManageExternalStorage_WithBypassFlag_BypassesDatabase() throws Exception { - installApp(APP_FM_BYPASS_DATABASE_OPS); - try { - final int fmUid = - getContext().getPackageManager().getPackageUid( - APP_FM_BYPASS_DATABASE_OPS.getPackageName(), 0); - allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE); - testAppBypassesDatabaseOps(APP_FM_BYPASS_DATABASE_OPS); - } finally { - uninstallAppNoThrow(APP_FM_BYPASS_DATABASE_OPS); - } + final int fmUid = getContext().getPackageManager().getPackageUid( + APP_FM_BYPASS_DATABASE_OPS.getPackageName(), 0); + allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE); + testAppBypassesDatabaseOps(APP_FM_BYPASS_DATABASE_OPS); } /** @@ -172,16 +166,10 @@ public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest { */ @Test public void testManageExternalStorage_targets30_BypassesDatabase() throws Exception { - installApp(APP_FM_TARGETS_30); - try { - final int fmUid = - getContext().getPackageManager().getPackageUid( - APP_FM_TARGETS_30.getPackageName(), 0); - allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE); - testAppBypassesDatabaseOps(APP_FM_TARGETS_30); - } finally { - uninstallAppNoThrow(APP_FM_TARGETS_30); - } + final int fmUid = getContext().getPackageManager().getPackageUid( + APP_FM_TARGETS_30.getPackageName(), 0); + allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE); + testAppBypassesDatabaseOps(APP_FM_TARGETS_30); } /** @@ -210,16 +198,10 @@ public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest { @Test @SdkSuppress(minSdkVersion = 31, codeName = "S") public void testSystemGallery_WithBypassFlag_BypassesDatabase() throws Exception { - installAppWithStoragePermissions(APP_SYSTEM_GALLERY_BYPASS_DB); - try { - final int sgUid = - getContext().getPackageManager().getPackageUid( - APP_SYSTEM_GALLERY_BYPASS_DB.getPackageName(), 0); - allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); - testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_BYPASS_DB); - } finally { - uninstallAppNoThrow(APP_SYSTEM_GALLERY_BYPASS_DB); - } + final int sgUid = getContext().getPackageManager().getPackageUid( + APP_SYSTEM_GALLERY_BYPASS_DB.getPackageName(), 0); + allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); + testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_BYPASS_DB); } /** @@ -228,16 +210,10 @@ public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest { */ @Test public void testSystemGallery_targets30_DoesntBypassDatabase() throws Exception { - installAppWithStoragePermissions(APP_SYSTEM_GALLERY_30); - try { - final int sgUid = - getContext().getPackageManager().getPackageUid( - APP_SYSTEM_GALLERY_30.getPackageName(), 0); - allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); - testAppDoesntBypassDatabaseOps(APP_SYSTEM_GALLERY_30); - } finally { - uninstallAppNoThrow(APP_SYSTEM_GALLERY_30); - } + final int sgUid = getContext().getPackageManager().getPackageUid( + APP_SYSTEM_GALLERY_30.getPackageName(), 0); + allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); + testAppDoesntBypassDatabaseOps(APP_SYSTEM_GALLERY_30); } /** @@ -248,16 +224,10 @@ public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest { @Test @SdkSuppress(minSdkVersion = 31, codeName = "S") public void testSystemGallery_targets30_WithBypassFlag_BypassesDatabase() throws Exception { - installAppWithStoragePermissions(APP_SYSTEM_GALLERY_30_BYPASS_DB); - try { - final int sgUid = - getContext().getPackageManager().getPackageUid( - APP_SYSTEM_GALLERY_30_BYPASS_DB.getPackageName(), 0); - allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); - testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_30_BYPASS_DB); - } finally { - uninstallAppNoThrow(APP_SYSTEM_GALLERY_30_BYPASS_DB); - } + final int sgUid = getContext().getPackageManager().getPackageUid( + APP_SYSTEM_GALLERY_30_BYPASS_DB.getPackageName(), 0); + allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS); + testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_30_BYPASS_DB); } private void testAppDoesntBypassDatabaseOps(TestApp app) throws Exception { diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java index 8e653a1f14f..4b17b65b93b 100644 --- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java +++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java @@ -48,6 +48,7 @@ import android.content.Intent; import android.database.Cursor; import android.media.ExifInterface; import android.net.Uri; +import android.scopedstorage.cts.lib.ScopedStorageBaseDeviceTest; import android.os.Bundle; import android.os.Environment; import android.os.FileUtils; @@ -78,7 +79,9 @@ import java.util.Collection; import java.util.List; /** - * Device-side test suite to verify redacted URI operations. + * We are in process of splitting CtsScopedStorageDeviceOnlyTest module into multiple ones. + * This is a temporary test class, and it will be deleted + * after all the links to new modules are created. */ @RunWith(Parameterized.class) @SdkSuppress(minSdkVersion = 31, codeName = "S") 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 bc6d64421be..8a0c53a4e2c 100644 --- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java +++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java @@ -139,6 +139,7 @@ import android.os.storage.StorageManager; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.scopedstorage.cts.lib.RedactionTestHelper; +import android.scopedstorage.cts.lib.ScopedStorageBaseDeviceTest; import android.system.ErrnoException; import android.system.Os; import android.system.StructStat; @@ -174,7 +175,9 @@ import java.util.HashMap; import java.util.List; /** - * Device-side test suite to verify scoped storage business logic. + * We are in process of splitting CtsScopedStorageDeviceOnlyTest module into multiple ones. + * This is a temporary test class, and it will be deleted + * after all the links to new modules are created. */ @RunWith(Parameterized.class) public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { @@ -182,7 +185,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { public static final byte[] BYTES_DATA1 = STR_DATA1.getBytes(); - static final String TAG = "ScopedStorageDeviceTest"; + static final String TAG = "ScopedStorageDeviceTestCopy"; static final String THIS_PACKAGE_NAME = getContext().getPackageName(); /** @@ -873,7 +876,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { try (InputStream in = getContext().getResources().openRawResource(R.raw.img_with_metadata); - FileOutputStream out = new FileOutputStream(jpgFile)) { + FileOutputStream out = new FileOutputStream(jpgFile)) { // Dump the image we have to external storage FileUtils.copy(in, out); // Sync file to disk to ensure file is fully written to the lower fs attempting to @@ -1211,9 +1214,9 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { File lowerCaseFile = new File(getDownloadDir(), "cache_consistency_for_case_insensitivity"); try (ParcelFileDescriptor upperCasePfd = - ParcelFileDescriptor.open(upperCaseFile, MODE_READ_WRITE | MODE_CREATE); + ParcelFileDescriptor.open(upperCaseFile, MODE_READ_WRITE | MODE_CREATE); ParcelFileDescriptor lowerCasePfd = - ParcelFileDescriptor.open(lowerCaseFile, MODE_READ_WRITE | MODE_CREATE)) { + ParcelFileDescriptor.open(lowerCaseFile, MODE_READ_WRITE | MODE_CREATE)) { assertRWR(upperCasePfd, lowerCasePfd); assertRWR(lowerCasePfd, upperCasePfd); @@ -1276,15 +1279,15 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { public void testReadStorageInvalidation() throws Exception { if (SdkLevel.isAtLeastT()) { testAppOpInvalidation( - APP_C, - new File(getDcimDir(), "read_storage.jpg"), - Manifest.permission.READ_MEDIA_IMAGES, - AppOpsManager.OPSTR_READ_MEDIA_IMAGES, - /* forWrite */ false); + APP_C, + new File(getDcimDir(), "read_storage.jpg"), + Manifest.permission.READ_MEDIA_IMAGES, + AppOpsManager.OPSTR_READ_MEDIA_IMAGES, + /* forWrite */ false); } else { testAppOpInvalidation(APP_C, new File(getDcimDir(), "read_storage.jpg"), - Manifest.permission.READ_EXTERNAL_STORAGE, - AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, /* forWrite */ false); + Manifest.permission.READ_EXTERNAL_STORAGE, + AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, /* forWrite */ false); } } diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/StableUrisTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/StableUrisTest.java index 07f57e79207..baf5a4f9b2f 100644 --- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/StableUrisTest.java +++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/StableUrisTest.java @@ -42,6 +42,7 @@ import android.net.Uri; import android.os.Bundle; import android.platform.test.annotations.FlakyTest; import android.provider.MediaStore; +import android.scopedstorage.cts.lib.ScopedStorageBaseDeviceTest; import android.util.Log; import androidx.test.core.app.ApplicationProvider; diff --git a/hostsidetests/scopedstorage/general/AndroidManifest.xml b/hostsidetests/scopedstorage/general/AndroidManifest.xml new file mode 100644 index 00000000000..47c38af6638 --- /dev/null +++ b/hostsidetests/scopedstorage/general/AndroidManifest.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.scopedstorage.cts.general"> + + <uses-sdk android:minSdkVersion="29" /> + + <queries> + <package android:name="android.scopedstorage.cts.testapp.A.withres" /> + <package android:name="android.scopedstorage.cts.testapp.B.noperms" /> + <package android:name="android.scopedstorage.cts.testapp.filemanager" /> + <package android:name="android.scopedstorage.cts.testapp.D" /> + <package android:name="android.scopedstorage.cts.testapp.general.only" /> + <package android:name="android.scopedstorage.cts.testapp.E.legacy" /> + <package android:name="android.scopedstorage.cts.testapp.E" /> + </queries> + + <application android:label="General Scoped Storage Device Tests"> + <uses-library android:name="android.test.runner" /> + <activity android:name="android.scopedstorage.cts.lib.GetResultActivity"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.scopedstorage.cts.general" + android:label="General Device-only scoped storage tests" /> + +</manifest> diff --git a/hostsidetests/scopedstorage/general/AndroidTest.xml b/hostsidetests/scopedstorage/general/AndroidTest.xml new file mode 100644 index 00000000000..772a4b735f7 --- /dev/null +++ b/hostsidetests/scopedstorage/general/AndroidTest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Runs general device-only tests for scoped storage"> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="CtsScopedStorageGeneralTest.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppELegacy.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppE.apk" /> + </target_preparer> + + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + + <option name="config-descriptor:metadata" key="component" value="framework" /> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" /> + <option name="test-suite-tag" value="cts" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.scopedstorage.cts.general" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> + + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.mediaprovider" /> + </object> +</configuration>
\ No newline at end of file diff --git a/hostsidetests/scopedstorage/general/src/android/scopedstorage/cts/general/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/general/src/android/scopedstorage/cts/general/ScopedStorageDeviceTest.java new file mode 100644 index 00000000000..d620b17bdd2 --- /dev/null +++ b/hostsidetests/scopedstorage/general/src/android/scopedstorage/cts/general/ScopedStorageDeviceTest.java @@ -0,0 +1,3590 @@ +/* + * 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.scopedstorage.cts.general; + +import static android.app.AppOpsManager.permissionToOp; +import static android.os.ParcelFileDescriptor.MODE_CREATE; +import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; +import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMatch; +import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMismatch; +import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromFile; +import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromRawResource; +import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA2; +import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2; +import static android.scopedstorage.cts.lib.TestUtils.addressStoragePermissions; +import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid; +import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory; +import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories; +import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory; +import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories; +import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains; +import static android.scopedstorage.cts.lib.TestUtils.assertFileContent; +import static android.scopedstorage.cts.lib.TestUtils.assertMountMode; +import static android.scopedstorage.cts.lib.TestUtils.assertThrows; +import static android.scopedstorage.cts.lib.TestUtils.canOpen; +import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs; +import static android.scopedstorage.cts.lib.TestUtils.canQueryOnUri; +import static android.scopedstorage.cts.lib.TestUtils.checkPermission; +import static android.scopedstorage.cts.lib.TestUtils.createFileAs; +import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs; +import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursivelyAs; +import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider; +import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow; +import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid; +import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand; +import static android.scopedstorage.cts.lib.TestUtils.fileExistsAs; +import static android.scopedstorage.cts.lib.TestUtils.getAlarmsDir; +import static android.scopedstorage.cts.lib.TestUtils.getAndroidDataDir; +import static android.scopedstorage.cts.lib.TestUtils.getAndroidMediaDir; +import static android.scopedstorage.cts.lib.TestUtils.getAudiobooksDir; +import static android.scopedstorage.cts.lib.TestUtils.getContentResolver; +import static android.scopedstorage.cts.lib.TestUtils.getDcimDir; +import static android.scopedstorage.cts.lib.TestUtils.getDocumentsDir; +import static android.scopedstorage.cts.lib.TestUtils.getDownloadDir; +import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir; +import static android.scopedstorage.cts.lib.TestUtils.getExternalMediaDir; +import static android.scopedstorage.cts.lib.TestUtils.getExternalObbDir; +import static android.scopedstorage.cts.lib.TestUtils.getExternalStorageDir; +import static android.scopedstorage.cts.lib.TestUtils.getFileMimeTypeFromDatabase; +import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase; +import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase; +import static android.scopedstorage.cts.lib.TestUtils.getFileSizeFromDatabase; +import static android.scopedstorage.cts.lib.TestUtils.getFileUri; +import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri; +import static android.scopedstorage.cts.lib.TestUtils.getMoviesDir; +import static android.scopedstorage.cts.lib.TestUtils.getMusicDir; +import static android.scopedstorage.cts.lib.TestUtils.getNotificationsDir; +import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir; +import static android.scopedstorage.cts.lib.TestUtils.getPodcastsDir; +import static android.scopedstorage.cts.lib.TestUtils.getRecordingsDir; +import static android.scopedstorage.cts.lib.TestUtils.getRingtonesDir; +import static android.scopedstorage.cts.lib.TestUtils.grantPermission; +import static android.scopedstorage.cts.lib.TestUtils.installApp; +import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions; +import static android.scopedstorage.cts.lib.TestUtils.listAs; +import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider; +import static android.scopedstorage.cts.lib.TestUtils.pollForPermission; +import static android.scopedstorage.cts.lib.TestUtils.queryAudioFile; +import static android.scopedstorage.cts.lib.TestUtils.queryFile; +import static android.scopedstorage.cts.lib.TestUtils.queryFileExcludingPending; +import static android.scopedstorage.cts.lib.TestUtils.queryImageFile; +import static android.scopedstorage.cts.lib.TestUtils.queryVideoFile; +import static android.scopedstorage.cts.lib.TestUtils.readExifMetadataFromTestApp; +import static android.scopedstorage.cts.lib.TestUtils.revokePermission; +import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid; +import static android.scopedstorage.cts.lib.TestUtils.setAttrAs; +import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert; +import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow; +import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert; +import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider; +import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed; +import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied; +import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalMediaDirViaRelativePath_allowed; +import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPrivateDirsViaRelativePath_denied; +import static android.system.OsConstants.F_OK; +import static android.system.OsConstants.O_APPEND; +import static android.system.OsConstants.O_CREAT; +import static android.system.OsConstants.O_EXCL; +import static android.system.OsConstants.O_RDWR; +import static android.system.OsConstants.O_TRUNC; +import static android.system.OsConstants.R_OK; +import static android.system.OsConstants.S_IRWXU; +import static android.system.OsConstants.W_OK; + +import static androidx.test.InstrumentationRegistry.getContext; +import static androidx.test.InstrumentationRegistry.getTargetContext; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; + +import android.Manifest; +import android.app.AppOpsManager; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.storage.StorageManager; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.scopedstorage.cts.lib.RedactionTestHelper; +import android.scopedstorage.cts.lib.ScopedStorageBaseDeviceTest; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructStat; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.test.filters.SdkSuppress; + +import com.android.compatibility.common.util.FeatureUtil; +import com.android.cts.install.lib.TestApp; +import com.android.modules.utils.build.SdkLevel; + +import com.google.common.io.Files; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +/** + * Device-side test suite to verify scoped storage business logic. + */ +@RunWith(Parameterized.class) +public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { + public static final String STR_DATA1 = "Just some random text"; + + public static final byte[] BYTES_DATA1 = STR_DATA1.getBytes(); + + static final String TAG = "ScopedStorageDeviceTest"; + static final String THIS_PACKAGE_NAME = getContext().getPackageName(); + + /** + * To help avoid flaky tests, give ourselves a unique nonce to be used for + * all filesystem paths, so that we don't risk conflicting with previous + * test runs. + */ + static final String NONCE = String.valueOf(System.nanoTime()); + + static final String TEST_DIRECTORY_NAME = "ScopedStorageDeviceTestDirectory" + NONCE; + + static final String AUDIO_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".mp3"; + static final String PLAYLIST_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".m3u"; + static final String SUBTITLE_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".srt"; + static final String VIDEO_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".mp4"; + static final String IMAGE_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".jpg"; + static final String NONMEDIA_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".pdf"; + + static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory"; + + // The following apps are installed before the tests are run via a target_preparer. + // See test config for details. + // An app with READ_EXTERNAL_STORAGE and READ_MEDIA_* permissions + private static final TestApp APP_A_HAS_RES = + new TestApp( + "TestAppA", + "android.scopedstorage.cts.testapp.A.withres", + 1, + false, + "CtsScopedStorageTestAppA.apk"); + // An app with no permissions + private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB", + "android.scopedstorage.cts.testapp.B.noperms", 1, false, + "CtsScopedStorageTestAppB.apk"); + // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission. + private static final TestApp APP_FM = new TestApp("TestAppFileManager", + "android.scopedstorage.cts.testapp.filemanager", 1, false, + "CtsScopedStorageTestAppFileManager.apk"); + // A legacy targeting app with RES and WES permissions + private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy", + "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk"); + private static final TestApp APP_E = new TestApp("TestAppE", + "android.scopedstorage.cts.testapp.E", 1, false, "CtsScopedStorageTestAppE.apk"); + private static final TestApp APP_E_LEGACY = new TestApp("TestAppELegacy", + "android.scopedstorage.cts.testapp.E.legacy", 1, false, + "CtsScopedStorageTestAppELegacy.apk"); + // APP_GENERAL_ONLY is not installed at test startup - please install before using. + private static final TestApp APP_GENERAL_ONLY = new TestApp("TestAppGeneralOnly", + "android.scopedstorage.cts.testapp.general.only", 1, false, + "CtsScopedStorageGeneralTestOnlyApp.apk"); + + private static final String[] SYSTEM_GALERY_APPOPS = { + AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO}; + private static final String OPSTR_MANAGE_EXTERNAL_STORAGE = + permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE); + + private static final String TRANSFORMS_DIR = ".transforms"; + private static final String TRANSFORMS_TRANSCODE_DIR = TRANSFORMS_DIR + "/" + "transcode"; + private static final String TRANSFORMS_SYNTHETIC_DIR = TRANSFORMS_DIR + "/" + "synthetic"; + + @Parameter(0) + public String mVolumeName; + + /** Parameters data. */ + @Parameters(name = "volume={0}") + public static Iterable<? extends Object> data() { + return ScopedStorageDeviceTest.getTestParameters(); + } + + @BeforeClass + public static void setupApps() throws Exception { + // File manager needs to be explicitly granted MES app op. + final int fmUid = + getContext().getPackageManager().getPackageUid(APP_FM.getPackageName(), + 0); + allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE); + + // Others are installed by target preparer with runtime permissions. + // Verify. + assertThat(checkPermission(APP_A_HAS_RES, + Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue(); + assertThat(checkPermission(APP_B_NO_PERMS, + Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse(); + assertThat(checkPermission(APP_D_LEGACY_HAS_RW, + Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue(); + assertThat(checkPermission(APP_D_LEGACY_HAS_RW, + Manifest.permission.WRITE_EXTERNAL_STORAGE)).isTrue(); + } + + @After + public void tearDown() throws Exception { + executeShellCommand("rm -r /sdcard/Android/data/com.android.shell"); + } + + @Before + public void setupExternalStorage() throws Exception { + super.setupExternalStorage(mVolumeName); + Log.i(TAG, "Using volume : " + mVolumeName); + } + + /** + * Test that we enforce certain media types can only be created in certain directories. + */ + @Test + public void testTypePathConformity() throws Exception { + final File dcimDir = getDcimDir(); + final File documentsDir = getDocumentsDir(); + final File downloadDir = getDownloadDir(); + final File moviesDir = getMoviesDir(); + final File musicDir = getMusicDir(); + final File picturesDir = getPicturesDir(); + // Only audio files can be created in Music + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(musicDir, NONMEDIA_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(musicDir, VIDEO_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(musicDir, IMAGE_FILE_NAME).createNewFile(); + }); + // Only video files can be created in Movies + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(moviesDir, NONMEDIA_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(moviesDir, AUDIO_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(moviesDir, IMAGE_FILE_NAME).createNewFile(); + }); + // Only image and video files can be created in DCIM + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(dcimDir, NONMEDIA_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(dcimDir, AUDIO_FILE_NAME).createNewFile(); + }); + // Only image and video files can be created in Pictures + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(picturesDir, NONMEDIA_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(picturesDir, AUDIO_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(picturesDir, PLAYLIST_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(dcimDir, SUBTITLE_FILE_NAME).createNewFile(); + }); + + assertCanCreateFile(new File(getAlarmsDir(), AUDIO_FILE_NAME)); + assertCanCreateFile(new File(getAudiobooksDir(), AUDIO_FILE_NAME)); + assertCanCreateFile(new File(dcimDir, IMAGE_FILE_NAME)); + assertCanCreateFile(new File(dcimDir, VIDEO_FILE_NAME)); + assertCanCreateFile(new File(documentsDir, AUDIO_FILE_NAME)); + assertCanCreateFile(new File(documentsDir, IMAGE_FILE_NAME)); + assertCanCreateFile(new File(documentsDir, NONMEDIA_FILE_NAME)); + assertCanCreateFile(new File(documentsDir, PLAYLIST_FILE_NAME)); + assertCanCreateFile(new File(documentsDir, SUBTITLE_FILE_NAME)); + assertCanCreateFile(new File(documentsDir, VIDEO_FILE_NAME)); + assertCanCreateFile(new File(downloadDir, AUDIO_FILE_NAME)); + assertCanCreateFile(new File(downloadDir, IMAGE_FILE_NAME)); + assertCanCreateFile(new File(downloadDir, NONMEDIA_FILE_NAME)); + assertCanCreateFile(new File(downloadDir, PLAYLIST_FILE_NAME)); + assertCanCreateFile(new File(downloadDir, SUBTITLE_FILE_NAME)); + assertCanCreateFile(new File(downloadDir, VIDEO_FILE_NAME)); + assertCanCreateFile(new File(moviesDir, VIDEO_FILE_NAME)); + assertCanCreateFile(new File(moviesDir, SUBTITLE_FILE_NAME)); + assertCanCreateFile(new File(musicDir, AUDIO_FILE_NAME)); + assertCanCreateFile(new File(musicDir, PLAYLIST_FILE_NAME)); + assertCanCreateFile(new File(getNotificationsDir(), AUDIO_FILE_NAME)); + assertCanCreateFile(new File(picturesDir, IMAGE_FILE_NAME)); + assertCanCreateFile(new File(picturesDir, VIDEO_FILE_NAME)); + assertCanCreateFile(new File(getPodcastsDir(), AUDIO_FILE_NAME)); + assertCanCreateFile(new File(getRingtonesDir(), AUDIO_FILE_NAME)); + + // No file whatsoever can be created in the top level directory + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(getExternalStorageDir(), NONMEDIA_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(getExternalStorageDir(), AUDIO_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(getExternalStorageDir(), IMAGE_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(getExternalStorageDir(), VIDEO_FILE_NAME).createNewFile(); + }); + } + + /** + * Test that we enforce certain media types can only be created in certain directories. + */ + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testTypePathConformity_recordingsDir() throws Exception { + final File recordingsDir = getRecordingsDir(); + + // Only audio files can be created in Recordings + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(recordingsDir, NONMEDIA_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(recordingsDir, VIDEO_FILE_NAME).createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + new File(recordingsDir, IMAGE_FILE_NAME).createNewFile(); + }); + + assertCanCreateFile(new File(recordingsDir, AUDIO_FILE_NAME)); + } + + /** + * Test that we can create a file in app's external files directory, + * and that we can write and read to/from the file. + */ + @Test + public void testCreateFileInAppExternalDir() throws Exception { + final File file = new File(getExternalFilesDir(), "text.txt"); + try { + assertThat(file.createNewFile()).isTrue(); + assertThat(file.delete()).isTrue(); + // Ensure the file is properly deleted and can be created again + assertThat(file.createNewFile()).isTrue(); + + // Write to file + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(BYTES_DATA1); + } + + // Read the same data from file + assertFileContent(file, BYTES_DATA1); + } finally { + file.delete(); + } + } + + /** + * Test that we can't create a file in another app's external files directory, + * and that we'll get the same error regardless of whether the app exists or not. + */ + @Test + public void testCreateFileInOtherAppExternalDir() throws Exception { + // Creating a file in a non existent package dir should return ENOENT, as expected + final File nonexistentPackageFileDir = new File( + getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "no.such.package")); + final File file1 = new File(nonexistentPackageFileDir, NONMEDIA_FILE_NAME); + assertThrows( + IOException.class, FILE_CREATION_ERROR_MESSAGE, () -> { + file1.createNewFile(); + }); + + // Creating a file in an existent package dir should give the same error string to avoid + // leaking installed app names, and we know the following directory exists because shell + // mkdirs it in test setup + final File shellPackageFileDir = new File( + getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "com.android.shell")); + final File file2 = new File(shellPackageFileDir, NONMEDIA_FILE_NAME); + assertThrows( + IOException.class, FILE_CREATION_ERROR_MESSAGE, () -> { + file1.createNewFile(); + }); + } + + /** + * Test that apps can't read/write files in another app's external files directory, + * and can do so in their own app's external file directory. + */ + @Test + public void testReadWriteFilesInOtherAppExternalDir() throws Exception { + final File videoFile = new File(getExternalFilesDir(), VIDEO_FILE_NAME); + + try { + // Create a file in app's external files directory + if (!videoFile.exists()) { + assertThat(videoFile.createNewFile()).isTrue(); + } + + // App A should not be able to read/write to other app's external files directory. + assertThat(canOpenFileAs(APP_A_HAS_RES, videoFile, false /* forWrite */)).isFalse(); + assertThat(canOpenFileAs(APP_A_HAS_RES, videoFile, true /* forWrite */)).isFalse(); + // App A should not be able to delete files in other app's external files + // directory. + assertThat(deleteFileAs(APP_A_HAS_RES, videoFile.getPath())).isFalse(); + + // Apps should have read/write access in their own app's external files directory. + assertThat(canOpen(videoFile, false /* forWrite */)).isTrue(); + assertThat(canOpen(videoFile, true /* forWrite */)).isTrue(); + // Apps should be able to delete files in their own app's external files directory. + assertThat(videoFile.delete()).isTrue(); + } finally { + videoFile.delete(); + } + } + + /** + * Test that we can contribute media without any permissions. + */ + @Test + public void testContributeMediaFile() throws Exception { + final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME); + + try { + assertThat(imageFile.createNewFile()).isTrue(); + + // Ensure that the file was successfully added to the MediaProvider database + assertThat(getFileOwnerPackageFromDatabase(imageFile)).isEqualTo(THIS_PACKAGE_NAME); + + // Try to write random data to the file + try (FileOutputStream fos = new FileOutputStream(imageFile)) { + fos.write(BYTES_DATA1); + fos.write(BYTES_DATA2); + } + + final byte[] expected = (STR_DATA1 + STR_DATA2).getBytes(); + assertFileContent(imageFile, expected); + + // Closing the file after writing will not trigger a MediaScan. Call scanFile to update + // file's entry in MediaProvider's database. + assertThat(MediaStore.scanFile(getContentResolver(), imageFile)).isNotNull(); + + // Ensure that the scan was completed and the file's size was updated. + assertThat(getFileSizeFromDatabase(imageFile)).isEqualTo( + BYTES_DATA1.length + BYTES_DATA2.length); + } finally { + imageFile.delete(); + } + // Ensure that delete makes a call to MediaProvider to remove the file from its database. + assertThat(getFileRowIdFromDatabase(imageFile)).isEqualTo(-1); + } + + @Test + public void testCreateAndDeleteEmptyDir() throws Exception { + final File externalFilesDir = getExternalFilesDir(); + // Remove directory in order to create it again + deleteRecursively(externalFilesDir); + + // Can create own external files dir + assertThat(externalFilesDir.mkdir()).isTrue(); + + final File dir1 = new File(externalFilesDir, "random_dir"); + // Can create dirs inside it + assertThat(dir1.mkdir()).isTrue(); + + final File dir2 = new File(dir1, "random_dir_inside_random_dir"); + // And create a dir inside the new dir + assertThat(dir2.mkdir()).isTrue(); + + // And can delete them all + assertThat(deleteRecursively(dir2)).isTrue(); + assertThat(deleteRecursively(dir1)).isTrue(); + assertThat(deleteRecursively(externalFilesDir)).isTrue(); + + // Can't create external dir for other apps + final File nonexistentPackageFileDir = new File( + externalFilesDir.getPath().replace(THIS_PACKAGE_NAME, "no.such.package")); + final File shellPackageFileDir = new File( + externalFilesDir.getPath().replace(THIS_PACKAGE_NAME, "com.android.shell")); + + assertThat(nonexistentPackageFileDir.mkdir()).isFalse(); + assertThat(shellPackageFileDir.mkdir()).isFalse(); + } + + @Test + public void testCantAccessOtherAppsContents() throws Exception { + final File mediaFile = new File(getPicturesDir(), IMAGE_FILE_NAME); + final File nonMediaFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME); + try { + assertThat(createFileAs(APP_B_NO_PERMS, mediaFile.getPath())).isTrue(); + assertThat(createFileAs(APP_B_NO_PERMS, nonMediaFile.getPath())).isTrue(); + + // We can still see that the files exist + assertThat(mediaFile.exists()).isTrue(); + assertThat(nonMediaFile.exists()).isTrue(); + + // But we can't access their content + assertThat(canOpen(mediaFile, /* forWrite */ false)).isFalse(); + assertThat(canOpen(nonMediaFile, /* forWrite */ true)).isFalse(); + assertThat(canOpen(mediaFile, /* forWrite */ false)).isFalse(); + assertThat(canOpen(nonMediaFile, /* forWrite */ true)).isFalse(); + } finally { + deleteFileAsNoThrow(APP_B_NO_PERMS, nonMediaFile.getPath()); + deleteFileAsNoThrow(APP_B_NO_PERMS, mediaFile.getPath()); + } + } + + @Test + public void testCantDeleteOtherAppsContents() throws Exception { + final File dirInDownload = new File(getDownloadDir(), TEST_DIRECTORY_NAME); + final File mediaFile = new File(dirInDownload, IMAGE_FILE_NAME); + final File nonMediaFile = new File(dirInDownload, NONMEDIA_FILE_NAME); + try { + assertThat(dirInDownload.mkdir()).isTrue(); + // Have another app create a media file in the directory + assertThat(createFileAs(APP_B_NO_PERMS, mediaFile.getPath())).isTrue(); + + // Can't delete the directory since it contains another app's content + assertThat(dirInDownload.delete()).isFalse(); + // Can't delete another app's content + assertThat(deleteRecursively(dirInDownload)).isFalse(); + + // Have another app create a non-media file in the directory + assertThat(createFileAs(APP_B_NO_PERMS, nonMediaFile.getPath())).isTrue(); + + // Can't delete the directory since it contains another app's content + assertThat(dirInDownload.delete()).isFalse(); + // Can't delete another app's content + assertThat(deleteRecursively(dirInDownload)).isFalse(); + + // Delete only the media file and keep the non-media file + assertThat(deleteFileAs(APP_B_NO_PERMS, mediaFile.getPath())).isTrue(); + // Directory now has only the non-media file contributed by another app, so we still + // can't delete it nor its content + assertThat(dirInDownload.delete()).isFalse(); + assertThat(deleteRecursively(dirInDownload)).isFalse(); + + // Delete the last file belonging to another app + assertThat(deleteFileAs(APP_B_NO_PERMS, nonMediaFile.getPath())).isTrue(); + // Create our own file + assertThat(nonMediaFile.createNewFile()).isTrue(); + + // Now that the directory only has content that was contributed by us, we can delete it + assertThat(deleteRecursively(dirInDownload)).isTrue(); + } finally { + deleteFileAsNoThrow(APP_B_NO_PERMS, nonMediaFile.getPath()); + deleteFileAsNoThrow(APP_B_NO_PERMS, mediaFile.getPath()); + // At this point, we're not sure who created this file, so we'll have both apps + // deleting it + mediaFile.delete(); + deleteRecursively(dirInDownload); + } + } + + /** + * Test that deleting uri corresponding to a file which was already deleted via filePath + * doesn't result in a security exception. + */ + @Test + public void testDeleteAlreadyUnlinkedFile() throws Exception { + final File nonMediaFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME); + try { + assertTrue(nonMediaFile.createNewFile()); + final Uri uri = MediaStore.scanFile(getContentResolver(), nonMediaFile); + assertNotNull(uri); + + // Delete the file via filePath + assertTrue(nonMediaFile.delete()); + + // If we delete nonMediaFile with ContentResolver#delete, it shouldn't result in a + // security exception. + assertThat(getContentResolver().delete(uri, Bundle.EMPTY)).isEqualTo(0); + } finally { + nonMediaFile.delete(); + } + } + + /** + * This test relies on the fact that {@link File#list} uses opendir internally, and that it + * returns {@code null} if opendir fails. + */ + @Test + public void testOpendirRestrictions() throws Exception { + // Opening a non existent package directory should fail, as expected + final File nonexistentPackageFileDir = new File( + getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "no.such.package")); + assertThat(nonexistentPackageFileDir.list()).isNull(); + + // Opening another package's external directory should fail as well, even if it exists + final File shellPackageFileDir = new File( + getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "com.android.shell")); + assertThat(shellPackageFileDir.list()).isNull(); + + // We can open our own external files directory + final String[] filesList = getExternalFilesDir().list(); + assertThat(filesList).isNotNull(); + + // We can open any public directory in external storage + assertThat(getDcimDir().list()).isNotNull(); + assertThat(getDownloadDir().list()).isNotNull(); + assertThat(getMoviesDir().list()).isNotNull(); + assertThat(getMusicDir().list()).isNotNull(); + + // We can open the root directory of external storage + final String[] topLevelDirs = getExternalStorageDir().list(); + assertThat(topLevelDirs).isNotNull(); + // TODO(b/145287327): This check fails on a device with no visible files. + // This can be fixed if we display default directories. + // assertThat(topLevelDirs).isNotEmpty(); + } + + @Test + public void testLowLevelFileIO() throws Exception { + String filePath = new File(getDownloadDir(), NONMEDIA_FILE_NAME).toString(); + try { + int createFlags = O_CREAT | O_RDWR; + int createExclFlags = createFlags | O_EXCL; + + FileDescriptor fd = Os.open(filePath, createExclFlags, S_IRWXU); + Os.close(fd); + assertThrows( + ErrnoException.class, () -> { + Os.open(filePath, createExclFlags, S_IRWXU); + }); + + fd = Os.open(filePath, createFlags, S_IRWXU); + try { + assertThat(Os.write(fd, + ByteBuffer.wrap(BYTES_DATA1))).isEqualTo(BYTES_DATA1.length); + assertFileContent(fd, BYTES_DATA1); + } finally { + Os.close(fd); + } + // should just append the data + fd = Os.open(filePath, createFlags | O_APPEND, S_IRWXU); + try { + assertThat(Os.write(fd, + ByteBuffer.wrap(BYTES_DATA2))).isEqualTo(BYTES_DATA2.length); + final byte[] expected = (STR_DATA1 + STR_DATA2).getBytes(); + assertFileContent(fd, expected); + } finally { + Os.close(fd); + } + // should overwrite everything + fd = Os.open(filePath, createFlags | O_TRUNC, S_IRWXU); + try { + final byte[] otherData = "this is different data".getBytes(); + assertThat(Os.write(fd, ByteBuffer.wrap(otherData))).isEqualTo(otherData.length); + assertFileContent(fd, otherData); + } finally { + Os.close(fd); + } + } finally { + new File(filePath).delete(); + } + } + + /** + * Test that media files from other packages are only visible to apps with storage permission. + */ + @Test + public void testListDirectoriesWithMediaFiles() throws Exception { + final File dcimDir = getDcimDir(); + final File dir = new File(dcimDir, TEST_DIRECTORY_NAME); + final File videoFile = new File(dir, VIDEO_FILE_NAME); + final String videoFileName = videoFile.getName(); + try { + if (!dir.exists()) { + assertThat(dir.mkdir()).isTrue(); + } + + assertThat(createFileAs(APP_B_NO_PERMS, videoFile.getPath())).isTrue(); + // App B should see TEST_DIRECTORY in DCIM and new file in TEST_DIRECTORY. + assertThat(listAs(APP_B_NO_PERMS, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME); + assertThat(listAs(APP_B_NO_PERMS, dir.getPath())).containsExactly(videoFileName); + + // App A has storage permission, so should see TEST_DIRECTORY in DCIM and new file + // in TEST_DIRECTORY. + assertThat(listAs(APP_A_HAS_RES, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME); + assertThat(listAs(APP_A_HAS_RES, dir.getPath())).containsExactly(videoFileName); + + // We are an app without storage permission; should see TEST_DIRECTORY in DCIM and + // should not see new file in new TEST_DIRECTORY. + assertThat(dcimDir.list()).asList().contains(TEST_DIRECTORY_NAME); + assertThat(dir.list()).asList().doesNotContain(videoFileName); + } finally { + deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getPath()); + deleteRecursively(dir); + } + } + + /** + * Test that app can't see non-media files created by other packages + */ + @Test + public void testListDirectoriesWithNonMediaFiles() throws Exception { + final File downloadDir = getDownloadDir(); + final File dir = new File(downloadDir, TEST_DIRECTORY_NAME); + final File pdfFile = new File(dir, NONMEDIA_FILE_NAME); + final String pdfFileName = pdfFile.getName(); + try { + if (!dir.exists()) { + assertThat(dir.mkdir()).isTrue(); + } + + // Have App B create non media file in the new directory. + assertThat(createFileAs(APP_B_NO_PERMS, pdfFile.getPath())).isTrue(); + + // App B should see TEST_DIRECTORY in downloadDir and new non media file in + // TEST_DIRECTORY. + assertThat(listAs(APP_B_NO_PERMS, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME); + assertThat(listAs(APP_B_NO_PERMS, dir.getPath())).containsExactly(pdfFileName); + + // APP A with storage permission should see TEST_DIRECTORY in downloadDir + // and should not see non media file in TEST_DIRECTORY. + assertThat(listAs(APP_A_HAS_RES, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME); + assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName); + } finally { + deleteFileAsNoThrow(APP_B_NO_PERMS, pdfFile.getPath()); + deleteRecursively(dir); + } + } + + /** + * Test that app can only see its directory in Android/data. + */ + @Test + public void testListFilesFromExternalFilesDirectory() throws Exception { + final String packageName = THIS_PACKAGE_NAME; + final File nonmediaFile = new File(getExternalFilesDir(), NONMEDIA_FILE_NAME); + + try { + // Create a file in app's external files directory + if (!nonmediaFile.exists()) { + assertThat(nonmediaFile.createNewFile()).isTrue(); + } + // App should see its directory and directories of shared packages. App should see all + // files and directories in its external directory. + assertDirectoryContains(nonmediaFile.getParentFile(), nonmediaFile); + + // App A should not see other app's external files directory despite RES. + assertThrows(IOException.class, + () -> listAs(APP_A_HAS_RES, getAndroidDataDir().getPath())); + assertThrows(IOException.class, + () -> listAs(APP_A_HAS_RES, getExternalFilesDir().getPath())); + } finally { + nonmediaFile.delete(); + } + } + + /** + * Test that app can see files and directories in Android/media. + */ + @Test + public void testListFilesFromExternalMediaDirectory() throws Exception { + final File videoFile = new File(getExternalMediaDir(), VIDEO_FILE_NAME); + + try { + // Create a file in app's external media directory + if (!videoFile.exists()) { + assertThat(videoFile.createNewFile()).isTrue(); + } + + // App should see its directory and other app's external media directories with media + // files. + assertDirectoryContains(videoFile.getParentFile(), videoFile); + + // App A with storage permission should see other app's external media directory. + // Apps with READ_EXTERNAL_STORAGE can list files in other app's external media + // directory. + assertThat(listAs(APP_A_HAS_RES, getAndroidMediaDir().getPath())) + .contains(THIS_PACKAGE_NAME); + assertThat(listAs(APP_A_HAS_RES, getExternalMediaDir().getPath())) + .containsExactly(videoFile.getName()); + } finally { + videoFile.delete(); + } + } + + @Test + public void testMetaDataRedaction() throws Exception { + File jpgFile = new File(getPicturesDir(), "img_metadata.jpg"); + try { + if (jpgFile.exists()) { + assertThat(jpgFile.delete()).isTrue(); + } + + HashMap<String, String> originalExif = + getExifMetadataFromRawResource(R.raw.img_with_metadata); + + try (InputStream in = + getContext().getResources().openRawResource(R.raw.img_with_metadata); + FileOutputStream out = new FileOutputStream(jpgFile)) { + // Dump the image we have to external storage + FileUtils.copy(in, out); + // Sync file to disk to ensure file is fully written to the lower fs attempting to + // open for redaction. Otherwise, the FUSE daemon might not accurately parse the + // EXIF tags and might misleadingly think there are not tags to redact + out.getFD().sync(); + + HashMap<String, String> exif = getExifMetadataFromFile(jpgFile); + assertExifMetadataMatch(exif, originalExif); + + HashMap<String, String> exifFromTestApp = + readExifMetadataFromTestApp(APP_A_HAS_RES, jpgFile.getPath()); + // App does not have AML; shouldn't have access to the same metadata. + assertExifMetadataMismatch(exifFromTestApp, originalExif); + + // TODO(b/146346138): Test that if we give APP_A write URI permission, + // it would be able to access the metadata. + } // Intentionally keep the original streams open during the test so bytes are more + // likely to be in the VFS cache from both file opens + } finally { + jpgFile.delete(); + } + } + + @Test + public void testOpenFilePathFirstWriteContentResolver() throws Exception { + String displayName = "open_file_path_write_content_resolver.jpg"; + File file = new File(getDcimDir(), displayName); + + try { + assertThat(file.createNewFile()).isTrue(); + + try (ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE); + ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw")) { + assertRWR(readPfd, writePfd); + assertUpperFsFd(writePfd); // With cache + } + } finally { + file.delete(); + } + } + + @Test + public void testOpenContentResolverFirstWriteContentResolver() throws Exception { + String displayName = "open_content_resolver_write_content_resolver.jpg"; + File file = new File(getDcimDir(), displayName); + + try { + assertThat(file.createNewFile()).isTrue(); + + try (ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw"); + ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE)) { + assertRWR(readPfd, writePfd); + assertLowerFsFdWithPassthrough(file.getPath(), writePfd); + } + } finally { + file.delete(); + } + } + + @Test + public void testOpenFilePathFirstWriteFilePath() throws Exception { + String displayName = "open_file_path_write_file_path.jpg"; + File file = new File(getDcimDir(), displayName); + + try { + assertThat(file.createNewFile()).isTrue(); + + try (ParcelFileDescriptor writePfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE); + ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw")) { + assertRWR(readPfd, writePfd); + assertUpperFsFd(readPfd); // With cache + } + } finally { + file.delete(); + } + } + + @Test + public void testOpenContentResolverFirstWriteFilePath() throws Exception { + String displayName = "open_content_resolver_write_file_path.jpg"; + File file = new File(getDcimDir(), displayName); + + try { + assertThat(file.createNewFile()).isTrue(); + + try (ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw"); + ParcelFileDescriptor writePfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE)) { + assertRWR(readPfd, writePfd); + assertLowerFsFdWithPassthrough(file.getPath(), readPfd); + } + } finally { + file.delete(); + } + } + + @Test + public void testOpenContentResolverWriteOnly() throws Exception { + String displayName = "open_content_resolver_write_only.jpg"; + File file = new File(getDcimDir(), displayName); + + try { + assertThat(file.createNewFile()).isTrue(); + + // We upgrade 'w' only to 'rw' + try (ParcelFileDescriptor writePfd = openWithMediaProvider(file, "w"); + ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw")) { + assertRWR(readPfd, writePfd); + assertRWR(writePfd, readPfd); // Can read on 'w' only pfd + assertLowerFsFdWithPassthrough(file.getPath(), writePfd); + assertLowerFsFdWithPassthrough(file.getPath(), readPfd); + } + } finally { + file.delete(); + } + } + + @Test + public void testOpenContentResolverDup() throws Exception { + String displayName = "open_content_resolver_dup.jpg"; + File file = new File(getDcimDir(), displayName); + + try { + file.delete(); + assertThat(file.createNewFile()).isTrue(); + + // Even if we close the original fd, since we have a dup open + // the FUSE IO should still bypass the cache + try (ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw"); + ParcelFileDescriptor writePfdDup = writePfd.dup(); + ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE)) { + writePfd.close(); + + assertRWR(readPfd, writePfdDup); + assertLowerFsFdWithPassthrough(file.getPath(), writePfdDup); + } + } finally { + file.delete(); + } + } + + @Test + public void testOpenContentResolverClose() throws Exception { + String displayName = "open_content_resolver_close.jpg"; + File file = new File(getDcimDir(), displayName); + + try { + byte[] readBuffer = new byte[10]; + byte[] writeBuffer = new byte[10]; + Arrays.fill(writeBuffer, (byte) 1); + + assertThat(file.createNewFile()).isTrue(); + + // Lower fs open and write + ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw"); + Os.pwrite(writePfd.getFileDescriptor(), writeBuffer, 0, 10, 0); + + // Close so upper fs open will not use direct_io + writePfd.close(); + + // Upper fs open and read without direct_io + try (ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE)) { + Os.pread(readPfd.getFileDescriptor(), readBuffer, 0, 10, 0); + + // Last write on lower fs is visible via upper fs + assertThat(readBuffer).isEqualTo(writeBuffer); + assertThat(readPfd.getStatSize()).isEqualTo(writeBuffer.length); + } + } finally { + file.delete(); + } + } + + @Test + public void testContentResolverDelete() throws Exception { + String displayName = "content_resolver_delete.jpg"; + File file = new File(getDcimDir(), displayName); + + try { + assertThat(file.createNewFile()).isTrue(); + + deleteWithMediaProvider(file); + + assertThat(file.exists()).isFalse(); + assertThat(file.createNewFile()).isTrue(); + } finally { + file.delete(); + } + } + + @Test + public void testContentResolverUpdate() throws Exception { + String oldDisplayName = "content_resolver_update_old.jpg"; + String newDisplayName = "content_resolver_update_new.jpg"; + File oldFile = new File(getDcimDir(), oldDisplayName); + File newFile = new File(getDcimDir(), newDisplayName); + + try { + assertThat(oldFile.createNewFile()).isTrue(); + // Publish the pending oldFile before updating with MediaProvider. Not publishing the + // file will make MP consider pending from FUSE as explicit IS_PENDING + final Uri uri = MediaStore.scanFile(getContentResolver(), oldFile); + assertNotNull(uri); + + updateDisplayNameWithMediaProvider(uri, + Environment.DIRECTORY_DCIM, oldDisplayName, newDisplayName); + + assertThat(oldFile.exists()).isFalse(); + assertThat(oldFile.createNewFile()).isTrue(); + assertThat(newFile.exists()).isTrue(); + assertThat(newFile.createNewFile()).isFalse(); + } finally { + oldFile.delete(); + newFile.delete(); + } + } + + void writeAndCheckMtime(final boolean append) throws Exception { + File file = new File(getDcimDir(), "update_modifies_mtime.jpg"); + + try { + assertThat(file.createNewFile()).isTrue(); + assertThat(file.exists()).isTrue(); + + final long creationTime = file.lastModified(); + + // File should exist + assertNotEquals(creationTime, 0L); + + // Sleep a bit more than 1 second because although + // File::lastModified() represents the duration in milliseconds, + // has 1 second precision. + // With lower sleep durations the test results flakey... + Thread.sleep(2000); + + // Modification time should be the same as long the file has not + // been modified + assertEquals(creationTime, file.lastModified()); + + // Sleep a bit more than 1 second because although + // File::lastModified() represents the duration in milliseconds, + // has 1 second precision. + // With lower sleep durations the test results flakey... + Thread.sleep(2000); + + // Assert we can write to the file + try (FileOutputStream fos = new FileOutputStream(file, append)) { + fos.write(BYTES_DATA1); + fos.close(); + } + + final long modificationTime = file.lastModified(); + + // As the file has been written, modification time should have + // changed + assertNotEquals(modificationTime, 0L); + assertNotEquals(modificationTime, creationTime); + } finally { + file.delete(); + } + } + + @Test + // There is a minor bug which, alghough fixed in sc-dev (aosp/1834457), + // cannot be propagated to the already released sc-release branche + // (b/234145920), where mainline-modules are tested. + // Skip this test in S to avoid failures in outdated targets. + @SdkSuppress(minSdkVersion = 33, codeName = "T") + public void testAppendUpdatesMtime() throws Exception { + writeAndCheckMtime(true); + } + + @Test + public void testWriteUpdatesMtime() throws Exception { + writeAndCheckMtime(false); + } + + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testDefaultNoIsolatedStorageFlag() throws Exception { + assertThat(Environment.isExternalStorageLegacy()).isFalse(); + } + + @Test + public void testCreateLowerCaseDeleteUpperCase() throws Exception { + File upperCase = new File(getDownloadDir(), "CREATE_LOWER_DELETE_UPPER"); + File lowerCase = new File(getDownloadDir(), "create_lower_delete_upper"); + + createDeleteCreate(lowerCase, upperCase); + } + + @Test + public void testCreateUpperCaseDeleteLowerCase() throws Exception { + File upperCase = new File(getDownloadDir(), "CREATE_UPPER_DELETE_LOWER"); + File lowerCase = new File(getDownloadDir(), "create_upper_delete_lower"); + + createDeleteCreate(upperCase, lowerCase); + } + + @Test + public void testCreateMixedCaseDeleteDifferentMixedCase() throws Exception { + File mixedCase1 = new File(getDownloadDir(), "CrEaTe_MiXeD_dElEtE_mIxEd"); + File mixedCase2 = new File(getDownloadDir(), "cReAtE_mIxEd_DeLeTe_MiXeD"); + + createDeleteCreate(mixedCase1, mixedCase2); + } + + @Test + public void testAndroidDataObbDoesNotForgetMount() throws Exception { + File dataDir = getContext().getExternalFilesDir(null); + File upperCaseDataDir = new File(dataDir.getPath().replace("Android/data", "ANDROID/DATA")); + + File obbDir = getContext().getObbDir(); + File upperCaseObbDir = new File(obbDir.getPath().replace("Android/obb", "ANDROID/OBB")); + + + StructStat beforeDataStruct = Os.stat(dataDir.getPath()); + StructStat beforeObbStruct = Os.stat(obbDir.getPath()); + + assertThat(dataDir.exists()).isTrue(); + assertThat(upperCaseDataDir.exists()).isTrue(); + assertThat(obbDir.exists()).isTrue(); + assertThat(upperCaseObbDir.exists()).isTrue(); + + StructStat afterDataStruct = Os.stat(upperCaseDataDir.getPath()); + StructStat afterObbStruct = Os.stat(upperCaseObbDir.getPath()); + + assertThat(beforeDataStruct.st_dev).isEqualTo(afterDataStruct.st_dev); + assertThat(beforeObbStruct.st_dev).isEqualTo(afterObbStruct.st_dev); + } + + @Test + public void testCacheConsistencyForCaseInsensitivity() throws Exception { + File upperCaseFile = new File(getDownloadDir(), "CACHE_CONSISTENCY_FOR_CASE_INSENSITIVITY"); + File lowerCaseFile = new File(getDownloadDir(), "cache_consistency_for_case_insensitivity"); + + try (ParcelFileDescriptor upperCasePfd = + ParcelFileDescriptor.open(upperCaseFile, MODE_READ_WRITE | MODE_CREATE); + ParcelFileDescriptor lowerCasePfd = + ParcelFileDescriptor.open(lowerCaseFile, MODE_READ_WRITE | MODE_CREATE)) { + + assertRWR(upperCasePfd, lowerCasePfd); + assertRWR(lowerCasePfd, upperCasePfd); + } finally { + upperCaseFile.delete(); + lowerCaseFile.delete(); + } + } + + @Test + public void testInsertDefaultPrimaryCaseInsensitiveCheck() throws Exception { + final File podcastsDir = getPodcastsDir(); + final File podcastsDirLowerCase = + new File(getExternalStorageDir(), Environment.DIRECTORY_PODCASTS.toLowerCase()); + final File fileInPodcastsDirLowerCase = new File(podcastsDirLowerCase, AUDIO_FILE_NAME); + try { + // Delete the directory if it already exists + if (podcastsDir.exists()) { + deleteRecursivelyAsLegacyApp(podcastsDir); + } + assertThat(podcastsDir.exists()).isFalse(); + assertThat(podcastsDirLowerCase.exists()).isFalse(); + + // Create the directory with lower case + assertThat(podcastsDirLowerCase.mkdir()).isTrue(); + // Because of case-insensitivity, even though directory is created + // with lower case, we should be able to see both directory names. + assertThat(podcastsDirLowerCase.exists()).isTrue(); + assertThat(podcastsDir.exists()).isTrue(); + + // File creation with lower case path of podcasts directory should not fail + assertThat(fileInPodcastsDirLowerCase.createNewFile()).isTrue(); + } finally { + fileInPodcastsDirLowerCase.delete(); + deleteRecursivelyAsLegacyApp(podcastsDirLowerCase); + podcastsDir.mkdirs(); + } + } + + private void createDeleteCreate(File create, File delete) throws Exception { + try { + assertThat(create.createNewFile()).isTrue(); + // Wait for the kernel to update the dentry cache. + Thread.sleep(100); + + assertThat(delete.delete()).isTrue(); + // Wait for the kernel to clean up the dentry cache. + Thread.sleep(100); + + assertThat(create.createNewFile()).isTrue(); + // Wait for the kernel to update the dentry cache. + Thread.sleep(100); + } finally { + create.delete(); + delete.delete(); + } + } + + @Test + public void testReadStorageInvalidation() throws Exception { + if (SdkLevel.isAtLeastT()) { + testAppOpInvalidation( + APP_E, + new File(getDcimDir(), "read_storage.jpg"), + Manifest.permission.READ_MEDIA_IMAGES, + AppOpsManager.OPSTR_READ_MEDIA_IMAGES, + /* forWrite */ false); + } else { + testAppOpInvalidation(APP_E, new File(getDcimDir(), "read_storage.jpg"), + Manifest.permission.READ_EXTERNAL_STORAGE, + AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, /* forWrite */ false); + } + } + + @Test + public void testWriteStorageInvalidation() throws Exception { + testAppOpInvalidation(APP_E_LEGACY, new File(getDcimDir(), "write_storage.jpg"), + Manifest.permission.WRITE_EXTERNAL_STORAGE, + AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE, /* forWrite */ true); + } + + @Test + public void testManageStorageInvalidation() throws Exception { + testAppOpInvalidation(APP_E, new File(getDownloadDir(), "manage_storage.pdf"), + /* permission */ null, OPSTR_MANAGE_EXTERNAL_STORAGE, /* forWrite */ true); + } + + @Test + public void testWriteImagesInvalidation() throws Exception { + testAppOpInvalidation(APP_E, new File(getDcimDir(), "write_images.jpg"), + /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, /* forWrite */ true); + } + + @Test + public void testWriteVideoInvalidation() throws Exception { + testAppOpInvalidation(APP_E, new File(getDcimDir(), "write_video.mp4"), + /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, /* forWrite */ true); + } + + @Test + public void testAccessMediaLocationInvalidation() throws Exception { + File imgFile = new File(getDcimDir(), "access_media_location.jpg"); + + try { + // Setup image with sensitive data on external storage + HashMap<String, String> originalExif = + getExifMetadataFromRawResource(R.raw.img_with_metadata); + try (InputStream in = + getContext().getResources().openRawResource(R.raw.img_with_metadata); + FileOutputStream out = new FileOutputStream(imgFile)) { + // Dump the image we have to external storage + FileUtils.copy(in, out); + // Sync file to disk to ensure file is fully written to the lower fs. + out.getFD().sync(); + } + HashMap<String, String> exif = getExifMetadataFromFile(imgFile); + assertExifMetadataMatch(exif, originalExif); + + // Install test app + installAppWithStoragePermissions(APP_GENERAL_ONLY); + + // Grant A_M_L and verify access to sensitive data + grantPermission(APP_GENERAL_ONLY.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION); + pollForPermission(APP_GENERAL_ONLY.getPackageName(), + Manifest.permission.ACCESS_MEDIA_LOCATION, /* granted */ true); + HashMap<String, String> exifFromTestApp = + readExifMetadataFromTestApp(APP_GENERAL_ONLY, imgFile.getPath()); + assertExifMetadataMatch(exifFromTestApp, originalExif); + + // Revoke A_M_L and verify sensitive data redaction + revokePermission( + APP_GENERAL_ONLY.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION); + // revokePermission waits for permission status to be updated, but MediaProvider still + // needs to get permission change callback and clear its permission cache. + pollForPermission(APP_GENERAL_ONLY.getPackageName(), + Manifest.permission.ACCESS_MEDIA_LOCATION, /* granted */ false); + Thread.sleep(500); + exifFromTestApp = readExifMetadataFromTestApp(APP_GENERAL_ONLY, imgFile.getPath()); + assertExifMetadataMismatch(exifFromTestApp, originalExif); + + // Re-grant A_M_L and verify access to sensitive data + grantPermission(APP_GENERAL_ONLY.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION); + // grantPermission waits for permission status to be updated, but MediaProvider still + // needs to get permission change callback and clear its permission cache. + pollForPermission(APP_GENERAL_ONLY.getPackageName(), + Manifest.permission.ACCESS_MEDIA_LOCATION, /* granted */ true); + Thread.sleep(500); + exifFromTestApp = readExifMetadataFromTestApp(APP_GENERAL_ONLY, imgFile.getPath()); + assertExifMetadataMatch(exifFromTestApp, originalExif); + } finally { + imgFile.delete(); + uninstallAppNoThrow(APP_GENERAL_ONLY); + } + } + + @Test + public void testAppUpdateInvalidation() throws Exception { + File file = new File(getDcimDir(), "app_update.jpg"); + try { + assertThat(file.createNewFile()).isTrue(); + + addressStoragePermissions(APP_E_LEGACY.getPackageName(), true); + grantPermission(APP_E_LEGACY.getPackageName(), + Manifest.permission.WRITE_EXTERNAL_STORAGE); // Grants write access for legacy + + // Legacy app can read and write media files contributed by others + assertThat(canOpenFileAs(APP_E_LEGACY, file, /* forWrite */ false)).isTrue(); + assertThat(canOpenFileAs(APP_E_LEGACY, file, /* forWrite */ true)).isTrue(); + + // Update to non-legacy + addressStoragePermissions(APP_E.getPackageName(), true); + grantPermission(APP_E_LEGACY.getPackageName(), + Manifest.permission.WRITE_EXTERNAL_STORAGE); // No effect for non-legacy + + // Non-legacy app can read media files contributed by others + assertThat(canOpenFileAs(APP_E, file, /* forWrite */ false)).isTrue(); + // But cannot write + assertThat(canOpenFileAs(APP_E, file, /* forWrite */ true)).isFalse(); + } finally { + file.delete(); + revokePermission(APP_E_LEGACY.getPackageName(), + Manifest.permission.WRITE_EXTERNAL_STORAGE); + } + } + + @Test + public void testAppReinstallInvalidation() throws Exception { + File file = new File(getDcimDir(), "app_reinstall.jpg"); + + try { + assertThat(file.createNewFile()).isTrue(); + + // Install + installAppWithStoragePermissions(APP_GENERAL_ONLY); + assertThat(canOpenFileAs(APP_GENERAL_ONLY, file, /* forWrite */ false)).isTrue(); + + // Re-install + uninstallAppNoThrow(APP_GENERAL_ONLY); + installApp(APP_GENERAL_ONLY); + assertThat(canOpenFileAs(APP_GENERAL_ONLY, file, /* forWrite */ false)).isFalse(); + } finally { + file.delete(); + uninstallAppNoThrow(APP_GENERAL_ONLY); + } + } + + private void testAppOpInvalidation(TestApp app, File file, @Nullable String permission, + String opstr, boolean forWrite) throws Exception { + try { + addressStoragePermissions(app.getPackageName(), false); + assertThat(file.createNewFile()).isTrue(); + assertAppOpInvalidation(app, file, permission, opstr, forWrite); + } finally { + file.delete(); + } + } + + /** If {@code permission} is null, appops are flipped, otherwise permissions are flipped */ + private void assertAppOpInvalidation(TestApp app, File file, @Nullable String permission, + String opstr, boolean forWrite) throws Exception { + String packageName = app.getPackageName(); + int uid = getContext().getPackageManager().getPackageUid(packageName, 0); + + // Deny + if (permission != null) { + revokePermission(packageName, permission); + } else { + denyAppOpsToUid(uid, opstr); + // TODO(191724755): Poll for AppOp state change instead + Thread.sleep(200); + } + assertThat(canOpenFileAs(app, file, forWrite)).isFalse(); + + // Grant + if (permission != null) { + grantPermission(packageName, permission); + } else { + allowAppOpsToUid(uid, opstr); + // TODO(191724755): Poll for AppOp state change instead + Thread.sleep(200); + } + assertThat(canOpenFileAs(app, file, forWrite)).isTrue(); + // Deny + if (permission != null) { + revokePermission(packageName, permission); + } else { + denyAppOpsToUid(uid, opstr); + // TODO(191724755): Poll for AppOp state change instead + Thread.sleep(200); + } + assertThat(canOpenFileAs(app, file, forWrite)).isFalse(); + } + + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + 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); + final File imageInAnObviouslyWrongPlace = new File(getMusicDir(), IMAGE_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(); + + // Assert we can write to the file + try (FileOutputStream fos = new FileOutputStream(otherAppImageFile)) { + fos.write(BYTES_DATA1); + } + + // Assert we can read from the file + assertFileContent(otherAppImageFile, BYTES_DATA1); + + // Assert has access to redacted information + RedactionTestHelper.assertConsistentNonRedactedAccess(otherAppImageFile, + R.raw.img_with_metadata); + + // Assert we can delete the file + assertThat(otherAppImageFile.delete()).isTrue(); + assertThat(otherAppImageFile.exists()).isFalse(); + + // Can create an image anywhere + assertCanCreateFile(topLevelImageFile); + assertCanCreateFile(imageInAnObviouslyWrongPlace); + + // Put the file back in its place and let APP B delete it + assertThat(otherAppImageFile.createNewFile()).isTrue(); + } finally { + deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImageFile.getAbsolutePath()); + otherAppImageFile.delete(); + denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + } + } + + @Test + public void testSystemGalleryAppHasNoFullAccessToAudio() throws Exception { + final File otherAppAudioFile = new File(getMusicDir(), "other_" + AUDIO_FILE_NAME); + final File topLevelAudioFile = new File(getExternalStorageDir(), AUDIO_FILE_NAME); + final File audioInAnObviouslyWrongPlace = new File(getPicturesDir(), AUDIO_FILE_NAME); + + try { + allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + + // Have another app create an audio file + assertThat(createFileAs(APP_B_NO_PERMS, otherAppAudioFile.getPath())).isTrue(); + assertThat(otherAppAudioFile.exists()).isTrue(); + + // Assert we can't access the file + assertThat(canOpen(otherAppAudioFile, /* forWrite */ false)).isFalse(); + assertThat(canOpen(otherAppAudioFile, /* forWrite */ true)).isFalse(); + + // Assert we can't delete the file + assertThat(otherAppAudioFile.delete()).isFalse(); + + // Can't create an audio file where it doesn't belong + assertThrows(IOException.class, "Operation not permitted", + () -> { + topLevelAudioFile.createNewFile(); + }); + assertThrows(IOException.class, "Operation not permitted", + () -> { + audioInAnObviouslyWrongPlace.createNewFile(); + }); + } finally { + deleteFileAs(APP_B_NO_PERMS, otherAppAudioFile.getPath()); + topLevelAudioFile.delete(); + audioInAnObviouslyWrongPlace.delete(); + denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + } + } + + @Test + public void testSystemGalleryCanRenameImagesAndVideos() throws Exception { + final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME); + final File imageFile = new File(getPicturesDir(), IMAGE_FILE_NAME); + final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME); + final File topLevelVideoFile = new File(getExternalStorageDir(), VIDEO_FILE_NAME); + final File musicFile = new File(getMusicDir(), AUDIO_FILE_NAME); + try { + allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + + // Have another app create a video file + assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue(); + assertThat(otherAppVideoFile.exists()).isTrue(); + + // Write some data to the file + try (FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) { + fos.write(BYTES_DATA1); + } + assertFileContent(otherAppVideoFile, BYTES_DATA1); + + // Assert we can rename the file and ensure the file has the same content + assertCanRenameFile(otherAppVideoFile, videoFile); + assertFileContent(videoFile, BYTES_DATA1); + // We can even move it to the top level directory + assertCanRenameFile(videoFile, topLevelVideoFile); + assertFileContent(topLevelVideoFile, BYTES_DATA1); + // And we can even convert it into an image file, because why not? + assertCanRenameFile(topLevelVideoFile, imageFile); + assertFileContent(imageFile, BYTES_DATA1); + + // We can convert it to a music file, but we won't have access to music file after + // renaming. + assertThat(imageFile.renameTo(musicFile)).isTrue(); + assertThat(getFileRowIdFromDatabase(musicFile)).isEqualTo(-1); + } finally { + deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppVideoFile.getAbsolutePath()); + imageFile.delete(); + videoFile.delete(); + topLevelVideoFile.delete(); + executeShellCommand("rm " + musicFile.getAbsolutePath()); + MediaStore.scanFile(getContentResolver(), musicFile); + denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + } + } + + /** + * Test that basic file path restrictions are enforced on file rename. + */ + @Test + public void testRenameFile() throws Exception { + final File downloadDir = getDownloadDir(); + final File nonMediaDir = new File(downloadDir, TEST_DIRECTORY_NAME); + final File pdfFile1 = new File(downloadDir, NONMEDIA_FILE_NAME); + final File pdfFile2 = new File(nonMediaDir, NONMEDIA_FILE_NAME); + final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME); + final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME); + final File videoFile3 = new File(downloadDir, VIDEO_FILE_NAME); + + try { + // Renaming non media file to media directory is not allowed. + assertThat(pdfFile1.createNewFile()).isTrue(); + assertCantRenameFile(pdfFile1, new File(getDcimDir(), NONMEDIA_FILE_NAME)); + assertCantRenameFile(pdfFile1, new File(getMusicDir(), NONMEDIA_FILE_NAME)); + assertCantRenameFile(pdfFile1, new File(getMoviesDir(), NONMEDIA_FILE_NAME)); + + // Renaming non media files to non media directories is allowed. + if (!nonMediaDir.exists()) { + assertThat(nonMediaDir.mkdirs()).isTrue(); + } + // App can rename pdfFile to non media directory. + assertCanRenameFile(pdfFile1, pdfFile2); + + assertThat(videoFile1.createNewFile()).isTrue(); + // App can rename video file to Movies directory + assertCanRenameFile(videoFile1, videoFile2); + // App can rename video file to Download directory + assertCanRenameFile(videoFile2, videoFile3); + } finally { + pdfFile1.delete(); + pdfFile2.delete(); + videoFile1.delete(); + videoFile2.delete(); + videoFile3.delete(); + deleteRecursively(nonMediaDir); + } + } + + /** + * Test that renaming file to different mime type is allowed. + */ + @Test + public void testRenameFileType() throws Exception { + final File pdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME); + final File videoFile = new File(getDcimDir(), VIDEO_FILE_NAME); + try { + assertThat(pdfFile.createNewFile()).isTrue(); + assertThat(videoFile.exists()).isFalse(); + // Moving pdfFile to DCIM directory is not allowed. + assertCantRenameFile(pdfFile, new File(getDcimDir(), NONMEDIA_FILE_NAME)); + // However, moving pdfFile to DCIM directory with changing the mime type to video is + // allowed. + assertCanRenameFile(pdfFile, videoFile); + + // On rename, MediaProvider database entry for pdfFile should be updated with new + // videoFile path and mime type should be updated to video/mp4. + assertThat(getFileMimeTypeFromDatabase(videoFile)).isEqualTo("video/mp4"); + } finally { + pdfFile.delete(); + videoFile.delete(); + } + } + + /** + * Test that renaming files overwrites files in newPath. + */ + @Test + public void testRenameAndReplaceFile() throws Exception { + final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME); + final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME); + final ContentResolver cr = getContentResolver(); + try { + assertThat(videoFile1.createNewFile()).isTrue(); + assertThat(videoFile2.createNewFile()).isTrue(); + final Uri uriVideoFile1 = MediaStore.scanFile(cr, videoFile1); + final Uri uriVideoFile2 = MediaStore.scanFile(cr, videoFile2); + + // Renaming a file which replaces file in newPath videoFile2 is allowed. + assertCanRenameFile(videoFile1, videoFile2); + + // Uri of videoFile2 should be accessible after rename. + try (ParcelFileDescriptor pfd = cr.openFileDescriptor(uriVideoFile2, "rw")) { + assertThat(pfd).isNotNull(); + } + + // Uri of videoFile1 should not be accessible after rename. + assertThrows(FileNotFoundException.class, + () -> { + cr.openFileDescriptor(uriVideoFile1, "rw"); + }); + } finally { + videoFile1.delete(); + videoFile2.delete(); + } + } + + /** + * Test that ScanFile() after renaming file extension updates the right + * MIME type from the file metadata. + */ + @Test + public void testScanUpdatesMimeTypeForRenameFileExtension() throws Exception { + final String audioFileName = "ScopedStorageDeviceTest_" + NONCE; + final File mpegFile = new File(getMusicDir(), audioFileName + ".mp3"); + final File nonMpegFile = new File(getMusicDir(), audioFileName + ".snd"); + try { + // Copy audio content to mpegFile + try (InputStream in = + getContext().getResources().openRawResource(R.raw.test_audio); + FileOutputStream out = new FileOutputStream(mpegFile)) { + FileUtils.copy(in, out); + out.getFD().sync(); + } + assertThat(MediaStore.scanFile(getContentResolver(), mpegFile)).isNotNull(); + assertThat(getFileMimeTypeFromDatabase(mpegFile)).isEqualTo("audio/mpeg"); + + // This rename changes MIME type from audio/mpeg to audio/basic + assertCanRenameFile(mpegFile, nonMpegFile); + assertThat(getFileMimeTypeFromDatabase(nonMpegFile)).isNotEqualTo("audio/mpeg"); + + assertThat(MediaStore.scanFile(getContentResolver(), nonMpegFile)).isNotNull(); + // Above scan should read file metadata and update the MIME type to audio/mpeg + assertThat(getFileMimeTypeFromDatabase(nonMpegFile)).isEqualTo("audio/mpeg"); + } finally { + mpegFile.delete(); + nonMpegFile.delete(); + } + } + + /** + * Test that app without write permission for file can't update the file. + */ + @Test + public void testRenameFileNotOwned() throws Exception { + final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME); + final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME); + try { + assertThat(createFileAs(APP_B_NO_PERMS, videoFile1.getAbsolutePath())).isTrue(); + // App can't rename a file owned by APP B. + assertCantRenameFile(videoFile1, videoFile2); + + assertThat(videoFile2.createNewFile()).isTrue(); + // App can't rename a file to videoFile1 which is owned by APP B. + assertCantRenameFile(videoFile2, videoFile1); + // TODO(b/146346138): Test that app with right URI permission should be able to rename + // the corresponding file + } finally { + deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile1.getAbsolutePath()); + videoFile2.delete(); + } + } + + /** + * Test that renaming file paths to an external directory such as Android/* and Android/* /* + * except Android/media/* /* is not allowed. + */ + @Test + public void testRenameFileToAppSpecificDir() throws Exception { + final File testFile = new File(getExternalMediaDir(), IMAGE_FILE_NAME); + final File testFileNew = new File(getExternalMediaDir(), NONMEDIA_FILE_NAME); + + try { + // Create a file in app's external media directory + if (!testFile.exists()) { + assertThat(testFile.createNewFile()).isTrue(); + } + + final String androidDirPath = getExternalStorageDir().getPath() + "/Android"; + + // Verify that we can't rename a file to Android/ or Android/data or + // Android/media directory + assertCantRenameFile(testFile, new File(androidDirPath, IMAGE_FILE_NAME)); + assertCantRenameFile(testFile, new File(androidDirPath + "/data", IMAGE_FILE_NAME)); + assertCantRenameFile(testFile, new File(androidDirPath + "/media", IMAGE_FILE_NAME)); + + // Verify that we can rename a file to app specific media directory. + assertCanRenameFile(testFile, testFileNew); + } finally { + testFile.delete(); + testFileNew.delete(); + } + } + + /** + * Test that renaming directories is allowed and aligns to default directory restrictions. + */ + @Test + public void testRenameDirectory() throws Exception { + final File dcimDir = getDcimDir(); + final File downloadDir = getDownloadDir(); + final String nonMediaDirectoryName = TEST_DIRECTORY_NAME + "NonMedia"; + final File nonMediaDirectory = new File(downloadDir, nonMediaDirectoryName); + final File pdfFile = new File(nonMediaDirectory, NONMEDIA_FILE_NAME); + + final String mediaDirectoryName = TEST_DIRECTORY_NAME + "Media"; + final File mediaDirectory1 = new File(dcimDir, mediaDirectoryName); + final File videoFile1 = new File(mediaDirectory1, VIDEO_FILE_NAME); + final File mediaDirectory2 = new File(downloadDir, mediaDirectoryName); + final File videoFile2 = new File(mediaDirectory2, VIDEO_FILE_NAME); + final File mediaDirectory3 = new File(getMoviesDir(), TEST_DIRECTORY_NAME); + final File videoFile3 = new File(mediaDirectory3, VIDEO_FILE_NAME); + final File mediaDirectory4 = new File(mediaDirectory3, mediaDirectoryName); + + try { + if (!nonMediaDirectory.exists()) { + assertThat(nonMediaDirectory.mkdirs()).isTrue(); + } + assertThat(pdfFile.createNewFile()).isTrue(); + // Move directory with pdf file to DCIM directory is not allowed. + assertThat(nonMediaDirectory.renameTo(new File(dcimDir, nonMediaDirectoryName))) + .isFalse(); + + if (!mediaDirectory1.exists()) { + assertThat(mediaDirectory1.mkdirs()).isTrue(); + } + assertThat(videoFile1.createNewFile()).isTrue(); + // Renaming to and from default directories is not allowed. + assertThat(mediaDirectory1.renameTo(dcimDir)).isFalse(); + // Moving top level default directories is not allowed. + assertCantRenameDirectory(downloadDir, new File(dcimDir, TEST_DIRECTORY_NAME), null); + + // Moving media directory to Download directory is allowed. + assertCanRenameDirectory(mediaDirectory1, mediaDirectory2, new File[] {videoFile1}, + new File[] {videoFile2}); + + // Moving media directory to Movies directory and renaming directory in new path is + // allowed. + assertCanRenameDirectory(mediaDirectory2, mediaDirectory3, new File[] {videoFile2}, + new File[] {videoFile3}); + + // Can't rename a mediaDirectory to non empty non Media directory. + assertCantRenameDirectory(mediaDirectory3, nonMediaDirectory, new File[] {videoFile3}); + // Can't rename a file to a directory. + assertCantRenameFile(videoFile3, mediaDirectory3); + // Can't rename a directory to file. + assertCantRenameDirectory(mediaDirectory3, pdfFile, null); + if (!mediaDirectory4.exists()) { + assertThat(mediaDirectory4.mkdir()).isTrue(); + } + // Can't rename a directory to subdirectory of itself. + assertCantRenameDirectory(mediaDirectory3, mediaDirectory4, new File[] {videoFile3}); + + } finally { + pdfFile.delete(); + deleteRecursively(nonMediaDirectory); + + videoFile1.delete(); + videoFile2.delete(); + videoFile3.delete(); + deleteRecursively(mediaDirectory1); + deleteRecursively(mediaDirectory2); + deleteRecursively(mediaDirectory3); + deleteRecursively(mediaDirectory4); + } + } + + /** + * Test that renaming directory checks file ownership permissions. + */ + @Test + public void testRenameDirectoryNotOwned() throws Exception { + final String mediaDirectoryName = TEST_DIRECTORY_NAME + "Media"; + File mediaDirectory1 = new File(getDcimDir(), mediaDirectoryName); + File mediaDirectory2 = new File(getMoviesDir(), mediaDirectoryName); + File videoFile = new File(mediaDirectory1, VIDEO_FILE_NAME); + + try { + if (!mediaDirectory1.exists()) { + assertThat(mediaDirectory1.mkdirs()).isTrue(); + } + assertThat(createFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue(); + // App doesn't have access to videoFile1, can't rename mediaDirectory1. + assertThat(mediaDirectory1.renameTo(mediaDirectory2)).isFalse(); + assertThat(videoFile.exists()).isTrue(); + // Test app can delete the file since the file is not moved to new directory. + assertThat(deleteFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue(); + } finally { + deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getAbsolutePath()); + deleteRecursively(mediaDirectory1); + deleteRecursively(mediaDirectory2); + } + } + + /** + * Test renaming empty directory is allowed + */ + @Test + public void testRenameEmptyDirectory() throws Exception { + final String emptyDirectoryName = TEST_DIRECTORY_NAME + "Media"; + File emptyDirectoryOldPath = new File(getDcimDir(), emptyDirectoryName); + File emptyDirectoryNewPath = new File(getMoviesDir(), TEST_DIRECTORY_NAME + "23456"); + try { + if (emptyDirectoryOldPath.exists()) { + executeShellCommand("rm -r " + emptyDirectoryOldPath.getPath()); + } + assertThat(emptyDirectoryOldPath.mkdirs()).isTrue(); + assertCanRenameDirectory(emptyDirectoryOldPath, emptyDirectoryNewPath, null, null); + } finally { + deleteRecursively(emptyDirectoryOldPath); + deleteRecursively(emptyDirectoryNewPath); + } + } + + /** + * Test that apps can create and delete hidden file. + */ + @Test + public void testCanCreateHiddenFile() throws Exception { + final File hiddenImageFile = new File(getDownloadDir(), ".hiddenFile" + IMAGE_FILE_NAME); + try { + assertThat(hiddenImageFile.createNewFile()).isTrue(); + // Write to hidden file is allowed. + try (FileOutputStream fos = new FileOutputStream(hiddenImageFile)) { + fos.write(BYTES_DATA1); + } + assertFileContent(hiddenImageFile, BYTES_DATA1); + + assertNotMediaTypeImage(hiddenImageFile); + + assertDirectoryContains(getDownloadDir(), hiddenImageFile); + assertThat(getFileRowIdFromDatabase(hiddenImageFile)).isNotEqualTo(-1); + + // We can delete hidden file + assertThat(hiddenImageFile.delete()).isTrue(); + assertThat(hiddenImageFile.exists()).isFalse(); + } finally { + hiddenImageFile.delete(); + } + } + + /** + * Test that FUSE upper-fs is consistent with lower-fs after the lower-fs fd is closed. + */ + @Test + public void testInodeStatConsistency() throws Exception { + File file = new File(getDcimDir(), IMAGE_FILE_NAME); + + try { + byte[] writeBuffer = new byte[10]; + Arrays.fill(writeBuffer, (byte) 1); + + assertThat(file.createNewFile()).isTrue(); + // Scanning a file is essential as files created via filepath will be marked + // as isPending, and we do not set listener for pending files as it can lead to + // performance overhead. See: I34611f0ee897dc676e7653beb7943aa6de58c55a. + MediaStore.scanFile(getContentResolver(), file); + + // File operation #1 (to lower-fs) + ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw"); + + // File operation #2 (to fuse). This caches the inode for the file. + file.exists(); + + // Write bytes directly to lower-fs + Os.pwrite(writePfd.getFileDescriptor(), writeBuffer, 0, 10, 0); + + // Close should invalidate inode cache for this file. + writePfd.close(); + Thread.sleep(1000); + + long fuseFileSize = file.length(); + assertThat(writeBuffer.length).isEqualTo(fuseFileSize); + } finally { + file.delete(); + } + } + + /** + * Test that apps can rename a hidden file. + */ + @Test + public void testCanRenameHiddenFile() throws Exception { + final String hiddenFileName = ".hidden" + IMAGE_FILE_NAME; + final File hiddenImageFile1 = new File(getDcimDir(), hiddenFileName); + final File hiddenImageFile2 = new File(getDownloadDir(), hiddenFileName); + final File imageFile = new File(getDownloadDir(), IMAGE_FILE_NAME); + try { + assertThat(hiddenImageFile1.createNewFile()).isTrue(); + assertCanRenameFile(hiddenImageFile1, hiddenImageFile2); + assertNotMediaTypeImage(hiddenImageFile2); + + // We can also rename hidden file to non-hidden + assertCanRenameFile(hiddenImageFile2, imageFile); + assertIsMediaTypeImage(imageFile); + + // We can rename non-hidden file to hidden + assertCanRenameFile(imageFile, hiddenImageFile1); + assertNotMediaTypeImage(hiddenImageFile1); + } finally { + hiddenImageFile1.delete(); + hiddenImageFile2.delete(); + imageFile.delete(); + } + } + + /** + * Test that files in hidden directory have MEDIA_TYPE=MEDIA_TYPE_NONE + */ + @Test + public void testHiddenDirectory() throws Exception { + final File hiddenDir = new File(getDownloadDir(), ".hidden" + TEST_DIRECTORY_NAME); + final File hiddenImageFile = new File(hiddenDir, IMAGE_FILE_NAME); + final File nonHiddenDir = new File(getDownloadDir(), TEST_DIRECTORY_NAME); + final File imageFile = new File(nonHiddenDir, IMAGE_FILE_NAME); + try { + if (!hiddenDir.exists()) { + assertThat(hiddenDir.mkdir()).isTrue(); + } + assertThat(hiddenImageFile.createNewFile()).isTrue(); + + assertNotMediaTypeImage(hiddenImageFile); + + // Renaming hiddenDir to nonHiddenDir makes the imageFile non-hidden and vice versa + assertCanRenameDirectory( + hiddenDir, nonHiddenDir, new File[] {hiddenImageFile}, new File[] {imageFile}); + assertIsMediaTypeImage(imageFile); + + assertCanRenameDirectory( + nonHiddenDir, hiddenDir, new File[] {imageFile}, new File[] {hiddenImageFile}); + assertNotMediaTypeImage(hiddenImageFile); + } finally { + hiddenImageFile.delete(); + imageFile.delete(); + deleteRecursively(hiddenDir); + deleteRecursively(nonHiddenDir); + } + } + + /** + * Test that files in directory with nomedia have MEDIA_TYPE=MEDIA_TYPE_NONE + */ + @Test + public void testHiddenDirectory_nomedia() throws Exception { + final File directoryNoMedia = new File(getDownloadDir(), "nomedia" + TEST_DIRECTORY_NAME); + final File noMediaFile = new File(directoryNoMedia, ".nomedia"); + final File imageFile = new File(directoryNoMedia, IMAGE_FILE_NAME); + final File videoFile = new File(directoryNoMedia, VIDEO_FILE_NAME); + try { + if (!directoryNoMedia.exists()) { + assertThat(directoryNoMedia.mkdir()).isTrue(); + } + assertThat(noMediaFile.createNewFile()).isTrue(); + assertThat(imageFile.createNewFile()).isTrue(); + + assertNotMediaTypeImage(imageFile); + + // Deleting the .nomedia file makes the parent directory non hidden. + noMediaFile.delete(); + MediaStore.scanFile(getContentResolver(), directoryNoMedia); + assertIsMediaTypeImage(imageFile); + + // Creating the .nomedia file makes the parent directory hidden again + assertThat(noMediaFile.createNewFile()).isTrue(); + MediaStore.scanFile(getContentResolver(), directoryNoMedia); + assertNotMediaTypeImage(imageFile); + + // Renaming the .nomedia file to non hidden file makes the parent directory non hidden. + assertCanRenameFile(noMediaFile, videoFile); + assertIsMediaTypeImage(imageFile); + } finally { + noMediaFile.delete(); + imageFile.delete(); + videoFile.delete(); + deleteRecursively(directoryNoMedia); + } + } + + /** + * Test that only file manager and app that created the hidden file can list it. + */ + @Test + public void testListHiddenFile() throws Exception { + final File dcimDir = getDcimDir(); + final String hiddenImageFileName = ".hidden" + IMAGE_FILE_NAME; + final File hiddenImageFile = new File(dcimDir, hiddenImageFileName); + try { + assertThat(hiddenImageFile.createNewFile()).isTrue(); + assertNotMediaTypeImage(hiddenImageFile); + + assertDirectoryContains(dcimDir, hiddenImageFile); + + // TestApp with read permissions can't see the hidden image file created by other app + assertThat(listAs(APP_A_HAS_RES, dcimDir.getAbsolutePath())) + .doesNotContain(hiddenImageFileName); + + // But file manager can + assertThat(listAs(APP_FM, dcimDir.getAbsolutePath())) + .contains(hiddenImageFileName); + + // Gallery cannot see the hidden image file created by other app + final int resAppUid = + getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(), + 0); + try { + allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS); + assertThat(listAs(APP_A_HAS_RES, dcimDir.getAbsolutePath())) + .doesNotContain(hiddenImageFileName); + } finally { + denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS); + } + } finally { + hiddenImageFile.delete(); + } + } + + @Test + public void testOpenPendingAndTrashed() throws Exception { + final File pendingImageFile = new File(getDcimDir(), IMAGE_FILE_NAME); + final File trashedVideoFile = new File(getPicturesDir(), VIDEO_FILE_NAME); + final File pendingPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME); + final File trashedPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME); + Uri pendingImgaeFileUri = null; + Uri trashedVideoFileUri = null; + Uri pendingPdfFileUri = null; + Uri trashedPdfFileUri = null; + try { + pendingImgaeFileUri = createPendingFile(pendingImageFile); + assertOpenPendingOrTrashed(pendingImgaeFileUri, /*isImageOrVideo*/ true); + + pendingPdfFileUri = createPendingFile(pendingPdfFile); + assertOpenPendingOrTrashed(pendingPdfFileUri, /*isImageOrVideo*/ false); + + trashedVideoFileUri = createTrashedFile(trashedVideoFile); + assertOpenPendingOrTrashed(trashedVideoFileUri, /*isImageOrVideo*/ true); + + trashedPdfFileUri = createTrashedFile(trashedPdfFile); + assertOpenPendingOrTrashed(trashedPdfFileUri, /*isImageOrVideo*/ false); + + } finally { + deleteFiles(pendingImageFile, pendingImageFile, trashedVideoFile, + trashedPdfFile); + deleteWithMediaProviderNoThrow(pendingImgaeFileUri, trashedVideoFileUri, + pendingPdfFileUri, trashedPdfFileUri); + } + } + + @Test + public void testListPendingAndTrashed() throws Exception { + final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME); + final File pdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME); + Uri imageFileUri = null; + Uri pdfFileUri = null; + try { + imageFileUri = createPendingFile(imageFile); + // Check that only owner package, file manager and system gallery can list pending image + // file. + assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true); + + trashFileAndAssert(imageFileUri); + // Check that only owner package, file manager and system gallery can list trashed image + // file. + assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true); + + pdfFileUri = createPendingFile(pdfFile); + // Check that only owner package, file manager can list pending non media file. + assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false); + + trashFileAndAssert(pdfFileUri); + // Check that only owner package, file manager can list trashed non media file. + assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false); + } finally { + deleteWithMediaProviderNoThrow(imageFileUri, pdfFileUri); + deleteFiles(imageFile, pdfFile); + } + } + + @Test + public void testDeletePendingAndTrashed_ownerCanDelete() throws Exception { + final File pendingVideoFile = new File(getDcimDir(), VIDEO_FILE_NAME); + final File trashedImageFile = new File(getPicturesDir(), IMAGE_FILE_NAME); + final File pendingPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME); + final File trashedPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME); + // Actual path of the file gets rewritten for pending and trashed files. + String pendingVideoFilePath = null; + String trashedImageFilePath = null; + String pendingPdfFilePath = null; + String trashedPdfFilePath = null; + try { + pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile)); + trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile)); + pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile)); + trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile)); + + // App can delete its own pending and trashed file. + assertCanDeletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath, + trashedPdfFilePath); + } finally { + deletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath, + trashedPdfFilePath); + deleteFiles(pendingVideoFile, trashedImageFile, pendingPdfFile, trashedPdfFile); + } + } + + @Test + public void testDeletePendingAndTrashed_otherAppCantDelete() throws Exception { + final File pendingVideoFile = new File(getDcimDir(), VIDEO_FILE_NAME); + final File trashedImageFile = new File(getPicturesDir(), IMAGE_FILE_NAME); + final File pendingPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME); + final File trashedPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME); + // Actual path of the file gets rewritten for pending and trashed files. + String pendingVideoFilePath = null; + String trashedImageFilePath = null; + String pendingPdfFilePath = null; + String trashedPdfFilePath = null; + try { + pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile)); + trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile)); + pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile)); + trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile)); + + // App can't delete other app's pending and trashed file. + assertCantDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath, + pendingPdfFilePath, trashedPdfFilePath); + } finally { + deletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath, + trashedPdfFilePath); + deleteFiles(pendingVideoFile, trashedImageFile, pendingPdfFile, trashedPdfFile); + } + } + + @Test + public void testDeletePendingAndTrashed_fileManagerCanDelete() throws Exception { + final File pendingVideoFile = new File(getDcimDir(), VIDEO_FILE_NAME); + final File trashedImageFile = new File(getPicturesDir(), IMAGE_FILE_NAME); + final File pendingPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME); + final File trashedPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME); + // Actual path of the file gets rewritten for pending and trashed files. + String pendingVideoFilePath = null; + String trashedImageFilePath = null; + String pendingPdfFilePath = null; + String trashedPdfFilePath = null; + try { + pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile)); + trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile)); + pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile)); + trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile)); + + // File Manager can delete any pending and trashed file + assertCanDeletePathsAs(APP_FM, pendingVideoFilePath, trashedImageFilePath, + pendingPdfFilePath, trashedPdfFilePath); + } finally { + deletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath, + trashedPdfFilePath); + deleteFiles(pendingVideoFile, trashedImageFile, pendingPdfFile, trashedPdfFile); + } + } + + @Test + public void testDeletePendingAndTrashed_systemGalleryCanDeleteMedia() throws Exception { + final File pendingVideoFile = new File(getDcimDir(), VIDEO_FILE_NAME); + final File trashedImageFile = new File(getPicturesDir(), IMAGE_FILE_NAME); + final File pendingPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME); + final File trashedPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME); + // Actual path of the file gets rewritten for pending and trashed files. + String pendingVideoFilePath = null; + String trashedImageFilePath = null; + String pendingPdfFilePath = null; + String trashedPdfFilePath = null; + try { + pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile)); + trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile)); + pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile)); + trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile)); + + // System Gallery can delete any pending and trashed image or video file. + final int resAppUid = + getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(), + 0); + try { + allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS); + assertTrue(isMediaTypeImageOrVideo(new File(pendingVideoFilePath))); + assertTrue(isMediaTypeImageOrVideo(new File(trashedImageFilePath))); + assertCanDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath); + + // System Gallery can't delete other app's pending and trashed pdf file. + assertFalse(isMediaTypeImageOrVideo(new File(pendingPdfFilePath))); + assertFalse(isMediaTypeImageOrVideo(new File(trashedPdfFilePath))); + assertCantDeletePathsAs(APP_A_HAS_RES, pendingPdfFilePath, trashedPdfFilePath); + } finally { + denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS); + } + } finally { + deletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath, + trashedPdfFilePath); + deleteFiles(pendingVideoFile, trashedImageFile, pendingPdfFile, trashedPdfFile); + } + } + + @Test + public void testSystemGalleryCanTrashOtherAndroidMediaFiles() throws Exception { + final File otherVideoFile = new File(getAndroidMediaDir(), + String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME)); + try { + allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + + assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue(); + + final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile); + assertNotNull(otherVideoUri); + + trashFileAndAssert(otherVideoUri); + untrashFileAndAssert(otherVideoUri); + } finally { + otherVideoFile.delete(); + denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + } + } + + @Test + public void testSystemGalleryCanUpdateOtherAndroidMediaFiles() throws Exception { + final File otherImageFile = new File(getAndroidMediaDir(), + String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME)); + final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME); + try { + allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + + assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue(); + + final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile); + assertNotNull(otherImageUri); + + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM); + // Test that we can move the file to "DCIM/" + assertWithMessage("Result of ContentResolver#update for " + otherImageUri + + " with values " + values) + .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY)) + .isEqualTo(1); + assertThat(updatedImageFileInDcim.exists()).isTrue(); + assertThat(otherImageFile.exists()).isFalse(); + + values.clear(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, + "Android/media/" + APP_B_NO_PERMS.getPackageName()); + // Test that we can move the file back to other app's owned path + assertWithMessage("Result of ContentResolver#update for " + otherImageUri + + " with values " + values) + .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY)) + .isEqualTo(1); + assertThat(otherImageFile.exists()).isTrue(); + } finally { + otherImageFile.delete(); + updatedImageFileInDcim.delete(); + denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + } + } + + @Test + public void testQueryOtherAppsFiles() throws Exception { + final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME); + final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME); + final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME); + final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg"); + try { + // Apps can't query other app's pending file, hence create file and publish it. + assertCreatePublishedFilesAs( + APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile); + + // Since the test doesn't have READ_EXTERNAL_STORAGE nor any other special permissions, + // it can't query for another app's contents. + assertCantQueryFile(otherAppImg); + assertCantQueryFile(otherAppMusic); + assertCantQueryFile(otherAppPdf); + assertCantQueryFile(otherHiddenFile); + } finally { + deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile); + } + } + + @Test + public void testSystemGalleryQueryOtherAppsFiles() throws Exception { + final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME); + final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME); + final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME); + final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg"); + try { + // Apps can't query other app's pending file, hence create file and publish it. + assertCreatePublishedFilesAs( + APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile); + + // System gallery apps have access to video and image files + allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + + assertCanQueryAndOpenFile(otherAppImg, "rw"); + // System gallery doesn't have access to hidden image files of other app + assertCantQueryFile(otherHiddenFile); + // But no access to PDFs or music files + assertCantQueryFile(otherAppMusic); + assertCantQueryFile(otherAppPdf); + } finally { + denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile); + } + } + + /** + * Test that System Gallery app can rename any directory under the default directories + * designated for images and videos, even if they contain other apps' contents that + * System Gallery doesn't have read access to. + */ + @Test + public void testSystemGalleryCanRenameImageAndVideoDirs() throws Exception { + final File dirInDcim = new File(getDcimDir(), TEST_DIRECTORY_NAME); + final File dirInPictures = new File(getPicturesDir(), TEST_DIRECTORY_NAME); + final File dirInPodcasts = new File(getPodcastsDir(), TEST_DIRECTORY_NAME); + final File otherAppImageFile1 = new File(dirInDcim, "other_" + IMAGE_FILE_NAME); + final File otherAppVideoFile1 = new File(dirInDcim, "other_" + VIDEO_FILE_NAME); + final File otherAppPdfFile1 = new File(dirInDcim, "other_" + NONMEDIA_FILE_NAME); + final File otherAppImageFile2 = new File(dirInPictures, "other_" + IMAGE_FILE_NAME); + final File otherAppVideoFile2 = new File(dirInPictures, "other_" + VIDEO_FILE_NAME); + final File otherAppPdfFile2 = new File(dirInPictures, "other_" + NONMEDIA_FILE_NAME); + try { + assertThat(dirInDcim.exists() || dirInDcim.mkdir()).isTrue(); + + executeShellCommand("touch " + otherAppPdfFile1); + MediaStore.scanFile(getContentResolver(), otherAppPdfFile1); + + allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + + assertCreateFilesAs(APP_A_HAS_RES, otherAppImageFile1, otherAppVideoFile1); + + // System gallery privileges don't go beyond DCIM, Movies and Pictures boundaries. + assertCantRenameDirectory(dirInDcim, dirInPodcasts, /*oldFilesList*/ null); + + // Rename should succeed, but System Gallery still can't access that PDF file! + assertCanRenameDirectory(dirInDcim, dirInPictures, + new File[] {otherAppImageFile1, otherAppVideoFile1}, + new File[] {otherAppImageFile2, otherAppVideoFile2}); + assertThat(getFileRowIdFromDatabase(otherAppPdfFile1)).isEqualTo(-1); + assertThat(getFileRowIdFromDatabase(otherAppPdfFile2)).isEqualTo(-1); + } finally { + executeShellCommand("rm " + otherAppPdfFile1); + executeShellCommand("rm " + otherAppPdfFile2); + MediaStore.scanFile(getContentResolver(), otherAppPdfFile1); + MediaStore.scanFile(getContentResolver(), otherAppPdfFile2); + otherAppImageFile1.delete(); + otherAppImageFile2.delete(); + otherAppVideoFile1.delete(); + otherAppVideoFile2.delete(); + otherAppPdfFile1.delete(); + otherAppPdfFile2.delete(); + deleteRecursively(dirInDcim); + deleteRecursively(dirInPictures); + denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + } + } + + /** + * Test that row ID corresponding to deleted path is restored on subsequent create. + */ + @Test + public void testCreateCanRestoreDeletedRowId() throws Exception { + final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME); + final ContentResolver cr = getContentResolver(); + + try { + assertThat(imageFile.createNewFile()).isTrue(); + final long oldRowId = getFileRowIdFromDatabase(imageFile); + assertThat(oldRowId).isNotEqualTo(-1); + final Uri uriOfOldFile = MediaStore.scanFile(cr, imageFile); + assertThat(uriOfOldFile).isNotNull(); + + assertThat(imageFile.delete()).isTrue(); + // We should restore old row Id corresponding to deleted imageFile. + assertThat(imageFile.createNewFile()).isTrue(); + assertThat(getFileRowIdFromDatabase(imageFile)).isEqualTo(oldRowId); + try (ParcelFileDescriptor pfd = cr.openFileDescriptor(uriOfOldFile, "rw")) { + assertThat(pfd).isNotNull(); + } + + + assertThat(imageFile.delete()).isTrue(); + assertThat(createFileAs(APP_B_NO_PERMS, imageFile.getAbsolutePath())).isTrue(); + + final Uri uriOfNewFile = MediaStore.scanFile(getContentResolver(), imageFile); + assertThat(uriOfNewFile).isNotNull(); + // We shouldn't restore deleted row Id if delete & create are called from different apps + assertThat(Integer.getInteger(uriOfNewFile.getLastPathSegment())) + .isNotEqualTo(oldRowId); + } finally { + imageFile.delete(); + deleteFileAsNoThrow(APP_B_NO_PERMS, imageFile.getAbsolutePath()); + } + } + + /** + * Test that row ID corresponding to deleted path is restored on subsequent rename. + */ + @Test + public void testRenameCanRestoreDeletedRowId() throws Exception { + final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME); + final File temporaryFile = new File(getDownloadDir(), IMAGE_FILE_NAME + "_.tmp"); + final ContentResolver cr = getContentResolver(); + + try { + assertThat(imageFile.createNewFile()).isTrue(); + final Uri oldUri = MediaStore.scanFile(cr, imageFile); + assertThat(oldUri).isNotNull(); + + Files.copy(imageFile, temporaryFile); + assertThat(imageFile.delete()).isTrue(); + assertCanRenameFile(temporaryFile, imageFile); + + final Uri newUri = MediaStore.scanFile(cr, imageFile); + assertThat(newUri).isNotNull(); + assertThat(newUri.getLastPathSegment()).isEqualTo(oldUri.getLastPathSegment()); + // oldUri of imageFile is still accessible after delete and rename. + try (ParcelFileDescriptor pfd = cr.openFileDescriptor(oldUri, "rw")) { + assertThat(pfd).isNotNull(); + } + } finally { + imageFile.delete(); + temporaryFile.delete(); + } + } + + @Test + public void testCantCreateOrRenameFileWithInvalidName() throws Exception { + File invalidFile = new File(getDownloadDir(), "<>"); + File validFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME); + try { + assertThrows(IOException.class, "Operation not permitted", + () -> { + invalidFile.createNewFile(); + }); + + assertThat(validFile.createNewFile()).isTrue(); + // We can't rename a file to a file name with invalid FAT characters. + assertCantRenameFile(validFile, invalidFile); + } finally { + invalidFile.delete(); + validFile.delete(); + } + } + + @Test + public void testRenameWithSpecialChars() throws Exception { + final String specialCharsSuffix = "'`~!@#$%^& ()_+-={}[];'.)"; + + final File fileSpecialChars = + new File(getDownloadDir(), NONMEDIA_FILE_NAME + specialCharsSuffix); + + final File dirSpecialChars = + new File(getDownloadDir(), TEST_DIRECTORY_NAME + specialCharsSuffix); + final File file1 = new File(dirSpecialChars, NONMEDIA_FILE_NAME); + final File fileSpecialChars1 = + new File(dirSpecialChars, NONMEDIA_FILE_NAME + specialCharsSuffix); + + final File renamedDir = new File(getDocumentsDir(), TEST_DIRECTORY_NAME); + final File file2 = new File(renamedDir, NONMEDIA_FILE_NAME); + final File fileSpecialChars2 = + new File(renamedDir, NONMEDIA_FILE_NAME + specialCharsSuffix); + try { + assertTrue(fileSpecialChars.createNewFile()); + if (!dirSpecialChars.exists()) { + assertTrue(dirSpecialChars.mkdir()); + } + assertTrue(file1.createNewFile()); + + // We can rename file name with special characters + assertCanRenameFile(fileSpecialChars, fileSpecialChars1); + + // We can rename directory name with special characters + assertCanRenameDirectory(dirSpecialChars, renamedDir, + new File[] {file1, fileSpecialChars1}, new File[] {file2, fileSpecialChars2}); + } finally { + file1.delete(); + file2.delete(); + fileSpecialChars.delete(); + fileSpecialChars1.delete(); + fileSpecialChars2.delete(); + deleteRecursively(dirSpecialChars); + deleteRecursively(renamedDir); + } + } + + /** + * Test that IS_PENDING is set for files created via filepath + */ + @Test + public void testPendingFromFuse() throws Exception { + final File pendingFile = new File(getDcimDir(), IMAGE_FILE_NAME); + final File otherPendingFile = new File(getDcimDir(), VIDEO_FILE_NAME); + try { + assertTrue(pendingFile.createNewFile()); + // Newly created file should have IS_PENDING set + try (Cursor c = queryFile(pendingFile, MediaStore.MediaColumns.IS_PENDING)) { + assertTrue(c.moveToFirst()); + assertThat(c.getInt(0)).isEqualTo(1); + } + + // If we query with MATCH_EXCLUDE, we should still see this pendingFile + try (Cursor c = queryFileExcludingPending(pendingFile, + MediaStore.MediaColumns.IS_PENDING)) { + assertThat(c.getCount()).isEqualTo(1); + assertTrue(c.moveToFirst()); + assertThat(c.getInt(0)).isEqualTo(1); + } + + assertNotNull(MediaStore.scanFile(getContentResolver(), pendingFile)); + + // IS_PENDING should be unset after the scan + try (Cursor c = queryFile(pendingFile, MediaStore.MediaColumns.IS_PENDING)) { + assertTrue(c.moveToFirst()); + assertThat(c.getInt(0)).isEqualTo(0); + } + + assertCreateFilesAs(APP_A_HAS_RES, otherPendingFile); + // We can't query other apps pending file from FUSE with MATCH_EXCLUDE + try (Cursor c = queryFileExcludingPending(otherPendingFile, + MediaStore.MediaColumns.IS_PENDING)) { + assertThat(c.getCount()).isEqualTo(0); + } + } finally { + pendingFile.delete(); + deleteFileAsNoThrow(APP_A_HAS_RES, otherPendingFile.getAbsolutePath()); + } + } + + /** + * Test that we don't allow renaming to top level directory + */ + @Test + public void testCantRenameToTopLevelDirectory() throws Exception { + final File topLevelDir1 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_1"); + final File topLevelDir2 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_2"); + final File nonTopLevelDir = new File(getDcimDir(), TEST_DIRECTORY_NAME); + try { + createDirectoryAsLegacyApp(topLevelDir1); + assertTrue(topLevelDir1.exists()); + + // We can't rename a top level directory to a top level directory + assertCantRenameDirectory(topLevelDir1, topLevelDir2, null); + + // However, we can rename a top level directory to non-top level directory. + assertCanRenameDirectory(topLevelDir1, nonTopLevelDir, null, null); + + // We can't rename a non-top level directory to a top level directory. + assertCantRenameDirectory(nonTopLevelDir, topLevelDir2, null); + } finally { + deleteRecursivelyAsLegacyApp(topLevelDir1); + deleteRecursivelyAsLegacyApp(topLevelDir2); + deleteRecursively(nonTopLevelDir); + } + } + + @Test + public void testCanCreateDefaultDirectory() throws Exception { + final File podcastsDir = getPodcastsDir(); + try { + if (podcastsDir.exists()) { + deleteRecursivelyAsLegacyApp(podcastsDir); + } + assertThat(podcastsDir.mkdir()).isTrue(); + } finally { + createDirectoryAsLegacyApp(podcastsDir); + } + } + + /** + * b/168830497: Test that app can write to file in DCIM/Camera even with .nomedia presence + */ + @Test + public void testCanWriteToDCIMCameraWithNomedia() throws Exception { + final File cameraDir = new File(getDcimDir(), "Camera"); + final File nomediaFile = new File(cameraDir, ".nomedia"); + Uri targetUri = null; + + try { + // Recreate required file and directory + if (cameraDir.exists()) { + // This is a work around to address a known inode cache inconsistency issue + // that occurs when test runs for the second time. + deleteRecursivelyAsLegacyApp(cameraDir); + } + + createDirectoryAsLegacyApp(cameraDir); + assertTrue(cameraDir.exists()); + + createFileAsLegacyApp(nomediaFile); + assertTrue(nomediaFile.exists()); + + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Camera"); + targetUri = getContentResolver().insert(getImageContentUri(), values, Bundle.EMPTY); + assertNotNull(targetUri); + + try (ParcelFileDescriptor pfd = + getContentResolver().openFileDescriptor(targetUri, "w")) { + assertThat(pfd).isNotNull(); + Os.write(pfd.getFileDescriptor(), ByteBuffer.wrap(BYTES_DATA1)); + } + + assertFileContent(new File(getFilePathFromUri(targetUri)), BYTES_DATA1); + } finally { + deleteWithMediaProviderNoThrow(targetUri); + deleteAsLegacyApp(nomediaFile); + deleteRecursivelyAsLegacyApp(cameraDir); + } + } + + /** + * b/182479650: Test that Screenshots directory is not hidden because of .nomedia presence + */ + @Test + public void testNoMediaDoesntHideSpecialDirectories() throws Exception { + for (File directory : new File [] { + getDcimDir(), + getDownloadDir(), + new File(getDcimDir(), "Camera"), + new File(getPicturesDir(), Environment.DIRECTORY_SCREENSHOTS), + new File(getMoviesDir(), Environment.DIRECTORY_SCREENSHOTS), + new File(getExternalStorageDir(), Environment.DIRECTORY_SCREENSHOTS) + }) { + assertNoMediaDoesntHideSpecialDirectories(directory); + } + } + + private void assertNoMediaDoesntHideSpecialDirectories(File directory) throws Exception { + final File nomediaFile = new File(directory, ".nomedia"); + final File videoFile = new File(directory, VIDEO_FILE_NAME); + Log.d(TAG, "Directory " + directory); + + try { + // Recreate required file and directory + if (!directory.exists()) { + Log.d(TAG, "mkdir directory " + directory); + createDirectoryAsLegacyApp(directory); + } + assertWithMessage("Exists " + directory).that(directory.exists()).isTrue(); + + Log.d(TAG, "CreateFileAs " + nomediaFile); + createFileAsLegacyApp(nomediaFile); + assertWithMessage("Exists " + nomediaFile).that(nomediaFile.exists()).isTrue(); + + createFileAsLegacyApp(videoFile); + assertWithMessage("Exists " + videoFile).that(videoFile.exists()).isTrue(); + final Uri targetUri = MediaStore.scanFile(getContentResolver(), videoFile); + assertWithMessage("Scan result for " + videoFile).that(targetUri) + .isNotNull(); + + assertWithMessage("Uri path segment for " + targetUri) + .that(targetUri.getPathSegments()).contains("video"); + + // Verify that the imageFile is not hidden because of .nomedia presence + assertWithMessage("Query as other app ") + .that(canQueryOnUri(APP_A_HAS_RES, targetUri)).isTrue(); + } finally { + deleteAsLegacyApp(videoFile); + deleteAsLegacyApp(nomediaFile); + deleteRecursivelyAsLegacyApp(directory); + } + } + + /** + * Test that readdir lists unsupported file types in default directories. + */ + @Test + public void testListUnsupportedFileType() throws Exception { + final File pdfFile = new File(getDcimDir(), NONMEDIA_FILE_NAME); + final File videoFile = new File(getMusicDir(), VIDEO_FILE_NAME); + try { + // TEST_APP_A with storage permission should not see pdf file in DCIM + createFileAsLegacyApp(pdfFile); + assertThat(pdfFile.exists()).isTrue(); + assertThat(MediaStore.scanFile(getContentResolver(), pdfFile)).isNotNull(); + + assertThat(listAs(APP_A_HAS_RES, getDcimDir().getPath())) + .doesNotContain(NONMEDIA_FILE_NAME); + + createFileAsLegacyApp(videoFile); + // We don't insert files to db for files created by shell. + assertThat(MediaStore.scanFile(getContentResolver(), videoFile)).isNotNull(); + // TEST_APP_A with storage permission should see video file in Music directory. + assertThat(listAs(APP_A_HAS_RES, getMusicDir().getPath())).contains(VIDEO_FILE_NAME); + } finally { + deleteAsLegacyApp(pdfFile); + deleteAsLegacyApp(videoFile); + MediaStore.scanFile(getContentResolver(), pdfFile); + MediaStore.scanFile(getContentResolver(), videoFile); + } + } + + /** + * Test that normal apps cannot access Android/data and Android/obb dirs of other apps + */ + @Test + public void testCantAccessOtherAppsExternalDirs() throws Exception { + File[] obbDirs = getContext().getObbDirs(); + File[] dataDirs = getContext().getExternalFilesDirs(null); + for (File obbDir : obbDirs) { + final File otherAppExternalObbDir = new File(obbDir.getPath().replace( + THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName())); + final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME); + try { + assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue(); + assertCannotReadOrWrite(file); + } finally { + deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath()); + } + } + 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(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue(); + assertCannotReadOrWrite(file); + } finally { + deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath()); + } + } + } + + /** + * Test that apps can't set attributes on another app's files. + */ + @Test + public void testCantSetAttrOtherAppsFile() throws Exception { + // This path's permission is checked in MediaProvider (directory/external media dir) + final File externalMediaPath = new File(getExternalMediaDir(), VIDEO_FILE_NAME); + + try { + // Create the files + if (!externalMediaPath.exists()) { + assertThat(externalMediaPath.createNewFile()).isTrue(); + } + + // APP A should not be able to setattr to other app's files. + assertWithMessage( + "setattr on directory/external media path [%s]", externalMediaPath.getPath()) + .that(setAttrAs(APP_A_HAS_RES, externalMediaPath.getPath())) + .isFalse(); + } finally { + externalMediaPath.delete(); + } + } + + /** + * b/171768780: Test that scan doesn't skip scanning renamed hidden file. + */ + @Test + public void testScanUpdatesMetadataForRenamedHiddenFile() throws Exception { + final File hiddenFile = new File(getPicturesDir(), ".hidden_" + IMAGE_FILE_NAME); + final File jpgFile = new File(getPicturesDir(), IMAGE_FILE_NAME); + try { + // Copy the image content to hidden file + try (InputStream in = + getContext().getResources().openRawResource(R.raw.img_with_metadata); + FileOutputStream out = new FileOutputStream(hiddenFile)) { + FileUtils.copy(in, out); + out.getFD().sync(); + } + Uri scanUri = MediaStore.scanFile(getContentResolver(), hiddenFile); + assertNotNull(scanUri); + + // Rename hidden file to non-hidden + assertCanRenameFile(hiddenFile, jpgFile); + + try (Cursor c = queryFile(jpgFile, MediaStore.MediaColumns.DATE_TAKEN)) { + assertTrue(c.moveToFirst()); + // The file is not scanned yet, hence the metadata is not updated yet. + assertThat(c.getString(0)).isNull(); + } + + // Scan the file to update the metadata for renamed hidden file. + scanUri = MediaStore.scanFile(getContentResolver(), jpgFile); + assertNotNull(scanUri); + + // Scan should be able to update metadata even if File.lastModifiedTime hasn't changed. + try (Cursor c = queryFile(jpgFile, MediaStore.MediaColumns.DATE_TAKEN)) { + assertTrue(c.moveToFirst()); + assertThat(c.getString(0)).isNotNull(); + } + } finally { + hiddenFile.delete(); + jpgFile.delete(); + } + } + + /** + * Tests that System Gallery apps cannot insert files in other app's private directories. + */ + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** + * Tests that System Gallery apps cannot update files in other app's private directories. + */ + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** + * This test is for operations to the calling app's own private packages. + */ + @Test + public void testInsertFromExternalDirsViaRelativePath() throws Exception { + verifyInsertFromExternalMediaDirViaRelativePath_allowed(); + verifyInsertFromExternalPrivateDirViaRelativePath_denied(); + } + + /** + * This test is for operations to the calling app's own private packages. + */ + @Test + public void testUpdateToExternalDirsViaRelativePath() throws Exception { + verifyUpdateToExternalMediaDirViaRelativePath_allowed(); + verifyUpdateToExternalPrivateDirsViaRelativePath_denied(); + } + + /** + * This test is for operations to the calling app's own private packages. + */ + @Test + public void testInsertFromExternalDirsViaRelativePathAsSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + verifyInsertFromExternalMediaDirViaRelativePath_allowed(); + verifyInsertFromExternalPrivateDirViaRelativePath_denied(); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** + * This test is for operations to the calling app's own private packages. + */ + @Test + public void testUpdateToExternalDirsViaRelativePathAsSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + verifyUpdateToExternalMediaDirViaRelativePath_allowed(); + verifyUpdateToExternalPrivateDirsViaRelativePath_denied(); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + @Test + public void testDeferredScanHidesPartialDatabaseRows() throws Exception { + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.IS_PENDING, 1); + // Insert a pending row + final Uri targetUri = getContentResolver().insert(getImageContentUri(), values, null); + try (InputStream in = + getContext().getResources().openRawResource(R.raw.img_with_metadata)) { + try (ParcelFileDescriptor pfd = + getContentResolver().openFileDescriptor(targetUri, "w")) { + // Write image content to the file + FileUtils.copy(in, new ParcelFileDescriptor.AutoCloseOutputStream(pfd)); + } + } + + // Verify that metadata is not updated yet. + try (Cursor c = getContentResolver().query(targetUri, new String[] { + MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null)) { + assertThat(c.moveToFirst()).isTrue(); + assertThat(c.getString(0)).isNull(); + } + // Get file path to use in the next query(). + final String imageFilePath = getFilePathFromUri(targetUri); + + values.put(MediaStore.MediaColumns.IS_PENDING, 0); + Bundle extras = new Bundle(); + extras.putBoolean(MediaStore.QUERY_ARG_DEFER_SCAN, true); + // Publish the file, but, defer the scan on update(). + assertThat(getContentResolver().update(targetUri, values, extras)).isEqualTo(1); + + // The update() above can return before scanning is complete. Verify that either we don't + // see the file in published files or if the file appears in the collection, it means that + // deferred scan is now complete, hence verify metadata is intact. + try (Cursor c = getContentResolver().query(getImageContentUri(), + new String[] {MediaStore.Images.ImageColumns.DATE_TAKEN}, + MediaStore.Files.FileColumns.DATA + "=?", new String[] {imageFilePath}, null)) { + if (c.getCount() == 1) { + // If the file appears in media collection as published file, verify that metadata + // is correct. + assertThat(c.moveToFirst()).isTrue(); + assertThat(c.getString(0)).isNotNull(); + Log.i(TAG, "Verified that deferred scan on " + imageFilePath + " is complete" + + " and hence metadata is updated"); + + } else { + assertThat(c.getCount()).isEqualTo(0); + Log.i(TAG, "Verified that " + imageFilePath + " was excluded in default query"); + } + } + } + + /** + * Test that renaming a file to {@link Environment#DIRECTORY_RINGTONES} sets + * {@link MediaStore.Audio.AudioColumns#IS_RINGTONE} + */ + + @Test + public void testRenameToRingtoneDirectory() throws Exception { + final File fileInDownloads = new File(getDownloadDir(), AUDIO_FILE_NAME); + final File fileInRingtones = new File(getRingtonesDir(), AUDIO_FILE_NAME); + + try { + assertThat(fileInDownloads.createNewFile()).isTrue(); + assertThat(MediaStore.scanFile(getContentResolver(), fileInDownloads)).isNotNull(); + + assertCanRenameFile(fileInDownloads, fileInRingtones); + + try (Cursor c = queryAudioFile(fileInRingtones, + MediaStore.Audio.AudioColumns.IS_RINGTONE)) { + assertTrue(c.moveToFirst()); + assertWithMessage("Expected " + MediaStore.Audio.AudioColumns.IS_RINGTONE + + " to be set after renaming to " + fileInRingtones) + .that(c.getInt(0)).isEqualTo(1); + } + + assertCanRenameFile(fileInRingtones, fileInDownloads); + + try (Cursor c = queryAudioFile(fileInDownloads, + MediaStore.Audio.AudioColumns.IS_RINGTONE)) { + assertTrue(c.moveToFirst()); + assertWithMessage("Expected " + MediaStore.Audio.AudioColumns.IS_RINGTONE + + " to be unset after renaming to " + fileInDownloads) + .that(c.getInt(0)).isEqualTo(0); + } + } finally { + fileInDownloads.delete(); + fileInRingtones.delete(); + } + } + + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testTransformsDirFileOperations() throws Exception { + final String path = Environment.getExternalStorageDirectory() + "/" + TRANSFORMS_DIR; + final File file = new File(path); + assertThat(file.exists()).isTrue(); + testTransformsDirCommon(file); + } + + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testTransformsSyntheticDirFileOperations() throws Exception { + final String path = + Environment.getExternalStorageDirectory() + "/" + TRANSFORMS_SYNTHETIC_DIR; + final File file = new File(path); + assertThat(file.exists()).isTrue(); + testTransformsDirCommon(file); + } + + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testTransformsTranscodeDirFileOperations() throws Exception { + final String path = + Environment.getExternalStorageDirectory() + "/" + TRANSFORMS_TRANSCODE_DIR; + final File file = new File(path); + assertThat(file.exists()).isFalse(); + testTransformsDirCommon(file); + } + + + /** + * Test mount modes for a platform signed app with ACCESS_MTP permission. + */ + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testMTPAppWithPlatformSignatureMountMode() throws Exception { + final String shellPackageName = "com.android.shell"; + final int uid = getContext().getPackageManager().getPackageUid(shellPackageName, 0); + assertMountMode(shellPackageName, uid, StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE); + } + + /** + * Test mount modes for ExternalStorageProvider and DownloadsProvider. + */ + @Test + @SdkSuppress(minSdkVersion = 31, codeName = "S") + public void testExternalStorageProviderAndDownloadsProvider() throws Exception { + // External Storage Provider and Downloads Provider are not supported on Wear OS + if (FeatureUtil.isWatch()) { + return; + } + assertWritableMountModeForProvider(DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY); + assertWritableMountModeForProvider(DocumentsContract.DOWNLOADS_PROVIDER_AUTHORITY); + } + + /** + * Test that normal apps cannot access Android/data and Android/obb dirs of other apps + */ + @Test + public void testCantProbeOtherAppsExternalDirs() throws Exception { + // Before fuse-bpf, apps could see other app's external storage + boolean expectToSee = !isFuseBpfEnabled() + && mVolumeName.equals(MediaStore.VOLUME_EXTERNAL); + String message = expectToSee + ? "Expected to see other app's private dirs" + : "Expected not to see other app's private dirs"; + + assertWithMessage(message) + .that(fileExistsAs(APP_B_NO_PERMS, new File(getExternalFilesDir().getParent()))) + .isEqualTo(expectToSee); + + assertWithMessage(message) + .that(fileExistsAs(APP_B_NO_PERMS, getExternalObbDir())) + .isEqualTo(expectToSee); + } + + private boolean isFuseBpfEnabled() throws Exception { + return executeShellCommand("getprop ro.fuse.bpf.is_running").trim().equals("true"); + } + + private void assertWritableMountModeForProvider(String auth) { + final ProviderInfo provider = getContext().getPackageManager() + .resolveContentProvider(auth, 0); + int uid = provider.applicationInfo.uid; + final String packageName = provider.applicationInfo.packageName; + + assertMountMode(packageName, uid, StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE); + } + + private boolean canRenameFile(File file) { + return file.renameTo(new File(file.getAbsolutePath() + "test")); + } + + private void testTransformsDirCommon(File file) throws Exception { + assertThat(file.delete()).isFalse(); + assertThat(canRenameFile(file)).isFalse(); + + final File newFile = new File(file.getAbsolutePath(), "test"); + assertThat(newFile.mkdir()).isFalse(); + assertThrows(IOException.class, () -> newFile.createNewFile()); + } + + 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 + * method doesn't uninstall given {@code testApp} at the end. + */ + private void assertOpenPendingOrTrashed(Uri uri, boolean isImageOrVideo) + throws Exception { + final File pendingOrTrashedFile = new File(getFilePathFromUri(uri)); + + // App can open its pending or trashed file for read or write + assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ false)); + assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ true)); + + // App with READ_EXTERNAL_STORAGE can't open other app's pending or trashed file for read or + // write + assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false)); + assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true)); + + assertTrue(canOpenFileAs(APP_FM, pendingOrTrashedFile, /*forWrite*/ false)); + assertTrue(canOpenFileAs(APP_FM, pendingOrTrashedFile, /*forWrite*/ true)); + + final int resAppUid = + getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(), 0); + try { + allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS); + if (isImageOrVideo) { + // System Gallery can open any pending or trashed image/video file for read or write + assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile)); + assertTrue(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false)); + assertTrue(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true)); + } else { + // System Gallery can't open other app's pending or trashed non-media file for read + // or write + assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile)); + assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false)); + assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true)); + } + } finally { + denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS); + } + } + + /** + * Checks restrictions for listing pending and trashed files by different apps. + */ + private void assertListPendingOrTrashed(Uri uri, File file, boolean isImageOrVideo) + throws Exception { + final String parentDirPath = file.getParent(); + assertTrue(new File(parentDirPath).isDirectory()); + + final List<String> listedFileNames = Arrays.asList(new File(parentDirPath).list()); + assertThat(listedFileNames).doesNotContain(file); + + final File pendingOrTrashedFile = new File(getFilePathFromUri(uri)); + + assertThat(listedFileNames).contains(pendingOrTrashedFile.getName()); + + // App with READ_EXTERNAL_STORAGE can't see other app's pending or trashed file. + assertThat(listAs(APP_A_HAS_RES, parentDirPath)).doesNotContain( + pendingOrTrashedFile.getName()); + + final int resAppUid = + getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(), 0); + // File Manager can see any pending or trashed file. + assertThat(listAs(APP_FM, parentDirPath)).contains(pendingOrTrashedFile.getName()); + + + try { + allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS); + if (isImageOrVideo) { + // System Gallery can see any pending or trashed image/video file. + assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile)); + assertThat(listAs(APP_A_HAS_RES, parentDirPath)).contains( + pendingOrTrashedFile.getName()); + } else { + // System Gallery can't see other app's pending or trashed non media file. + assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile)); + assertThat(listAs(APP_A_HAS_RES, parentDirPath)) + .doesNotContain(pendingOrTrashedFile.getName()); + } + } finally { + denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS); + } + } + + private Uri createPendingFile(File pendingFile) throws Exception { + assertTrue(pendingFile.createNewFile()); + + final ContentResolver cr = getContentResolver(); + final Uri trashedFileUri = MediaStore.scanFile(cr, pendingFile); + assertNotNull(trashedFileUri); + + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.IS_PENDING, 1); + assertEquals(1, cr.update(trashedFileUri, values, Bundle.EMPTY)); + + return trashedFileUri; + } + + private Uri createTrashedFile(File trashedFile) throws Exception { + assertTrue(trashedFile.createNewFile()); + + final ContentResolver cr = getContentResolver(); + final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile); + assertNotNull(trashedFileUri); + + trashFileAndAssert(trashedFileUri); + return trashedFileUri; + } + + /** + * Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to + * multiple db rows, file path is extracted from the first db row of the database query result. + */ + private String getFilePathFromUri(Uri uri) { + final String[] projection = new String[] {MediaStore.MediaColumns.DATA}; + try (Cursor c = getContentResolver().query(uri, projection, null, null)) { + assertTrue(c.moveToFirst()); + return c.getString(0); + } + } + + private boolean isMediaTypeImageOrVideo(File file) { + return queryImageFile(file).getCount() == 1 || queryVideoFile(file).getCount() == 1; + } + + private static void assertIsMediaTypeImage(File file) { + final Cursor c = queryImageFile(file); + assertEquals(1, c.getCount()); + } + + private static void assertNotMediaTypeImage(File file) { + final Cursor c = queryImageFile(file); + assertEquals(0, c.getCount()); + } + + private static void assertCantQueryFile(File file) { + assertThat(getFileUri(file)).isNull(); + // Confirm that file exists in the database. + assertNotNull(MediaStore.scanFile(getContentResolver(), file)); + } + + private static void assertCreateFilesAs(TestApp testApp, File... files) throws Exception { + for (File file : files) { + assertFalse("File already exists: " + file, file.exists()); + assertTrue("Failed to create file " + file + " on behalf of " + + testApp.getPackageName(), createFileAs(testApp, file.getPath())); + } + } + + /** + * Makes {@code testApp} create {@code files}. Publishes {@code files} by scanning the file. + * Pending files from FUSE are not visible to other apps via MediaStore APIs. We have to publish + * the file or make the file non-pending to make the file visible to other apps. + * <p> + * Note that this method can only be used for scannable files. + */ + private static void assertCreatePublishedFilesAs(TestApp testApp, File... files) + throws Exception { + for (File file : files) { + assertTrue("Failed to create published file " + file + " on behalf of " + + testApp.getPackageName(), createFileAs(testApp, file.getPath())); + assertNotNull("Failed to scan " + file, + MediaStore.scanFile(getContentResolver(), file)); + } + } + + + private static void deleteFilesAs(TestApp testApp, File... files) throws Exception { + for (File file : files) { + deleteFileAs(testApp, file.getPath()); + } + } + private static void assertCanDeletePathsAs(TestApp testApp, String... filePaths) + throws Exception { + for (String path: filePaths) { + assertTrue("Failed to delete file " + path + " on behalf of " + + testApp.getPackageName(), deleteFileAs(testApp, path)); + } + } + + private static void assertCantDeletePathsAs(TestApp testApp, String... filePaths) + throws Exception { + for (String path: filePaths) { + assertFalse("Deleting " + path + " on behalf of " + testApp.getPackageName() + + " was expected to fail", deleteFileAs(testApp, path)); + } + } + + private void deleteFiles(File... files) { + for (File file: files) { + if (file == null) continue; + file.delete(); + } + } + + private void deletePaths(String... paths) { + for (String path: paths) { + if (path == null) continue; + new File(path).delete(); + } + } + + private static void assertCanDeletePaths(String... filePaths) { + for (String filePath : filePaths) { + assertTrue("Failed to delete " + filePath, + new File(filePath).delete()); + } + } + + /** + * For possible values of {@code mode}, look at {@link android.content.ContentProvider#openFile} + */ + private static void assertCanQueryAndOpenFile(File file, String mode) throws IOException { + // This call performs the query + final Uri fileUri = getFileUri(file); + // The query succeeds iff it didn't return null + assertThat(fileUri).isNotNull(); + // Now we assert that we can open the file through ContentResolver + try (ParcelFileDescriptor pfd = + getContentResolver().openFileDescriptor(fileUri, mode)) { + assertThat(pfd).isNotNull(); + } + } + + /** + * Assert that the last read in: read - write - read using {@code readFd} and {@code writeFd} + * see the last write. {@code readFd} and {@code writeFd} are fds pointing to the same + * underlying file on disk but may be derived from different mount points and in that case + * have separate VFS caches. + */ + private void assertRWR(ParcelFileDescriptor readPfd, ParcelFileDescriptor writePfd) + throws Exception { + FileDescriptor readFd = readPfd.getFileDescriptor(); + FileDescriptor writeFd = writePfd.getFileDescriptor(); + + byte[] readBuffer = new byte[10]; + byte[] writeBuffer = new byte[10]; + Arrays.fill(writeBuffer, (byte) 1); + + // Write so readFd has content to read from next + Os.pwrite(readFd, readBuffer, 0, 10, 0); + // Read so readBuffer is in readFd's mount VFS cache + Os.pread(readFd, readBuffer, 0, 10, 0); + + // Assert that readBuffer is zeroes + assertThat(readBuffer).isEqualTo(new byte[10]); + + // Write so writeFd and readFd should now see writeBuffer + Os.pwrite(writeFd, writeBuffer, 0, 10, 0); + + // Read so the last write can be verified on readFd + Os.pread(readFd, readBuffer, 0, 10, 0); + + // Assert that the last write is indeed visible via readFd + assertThat(readBuffer).isEqualTo(writeBuffer); + assertThat(readPfd.getStatSize()).isEqualTo(writePfd.getStatSize()); + } + + private void assertStartsWith(String actual, String prefix) throws Exception { + String message = "String \"" + actual + "\" should start with \"" + prefix + "\""; + + assertWithMessage(message).that(actual).startsWith(prefix); + } + + private void assertLowerFsFd(ParcelFileDescriptor pfd) throws Exception { + String path = Os.readlink("/proc/self/fd/" + pfd.getFd()); + String prefix = "/storage"; + + assertStartsWith(path, prefix); + } + + private void assertUpperFsFd(ParcelFileDescriptor pfd) throws Exception { + String path = Os.readlink("/proc/self/fd/" + pfd.getFd()); + String prefix = "/mnt/user"; + + assertStartsWith(path, prefix); + } + + private void assertLowerFsFdWithPassthrough(final String path, ParcelFileDescriptor pfd) + throws Exception { + final ContentResolver resolver = getTargetContext().getContentResolver(); + final Bundle res = resolver.call(MediaStore.AUTHORITY, "uses_fuse_passthrough", path, null); + boolean passthroughEnabled = res.getBoolean("uses_fuse_passthrough_result"); + + if (passthroughEnabled) { + assertUpperFsFd(pfd); + } else { + assertLowerFsFd(pfd); + } + } + + private static void assertCanCreateFile(File file) throws IOException { + // If the file somehow managed to survive a previous run, then the test app was uninstalled + // and MediaProvider will remove our its ownership of the file, so it's not guaranteed that + // we can create nor delete it. + if (!file.exists()) { + assertThat(file.createNewFile()).isTrue(); + assertThat(file.delete()).isTrue(); + } else { + Log.w(TAG, + "Couldn't assertCanCreateFile(" + file + ") because file existed prior to " + + "running the test!"); + } + } + + private static void assertCannotReadOrWrite(File file) + throws Exception { + // App data directories have different 'x' bits on upgrading vs new devices. Let's not + // check 'exists', by passing checkExists=false. But assert this app cannot read or write + // the other app's file. + assertAccess(file, false /* value is moot */, false /* canRead */, + false /* canWrite */, false /* checkExists */); + } + + private static void assertAccess(File file, boolean exists, boolean canRead, boolean canWrite) + throws Exception { + assertAccess(file, exists, canRead, canWrite, true /* checkExists */); + } + + private static void assertAccess(File file, boolean exists, boolean canRead, boolean canWrite, + boolean checkExists) throws Exception { + if (checkExists) { + assertThat(file.exists()).isEqualTo(exists); + } + assertThat(file.canRead()).isEqualTo(canRead); + assertThat(file.canWrite()).isEqualTo(canWrite); + if (file.isDirectory()) { + if (checkExists) { + assertThat(file.canExecute()).isEqualTo(exists); + } + } else { + assertThat(file.canExecute()).isFalse(); // Filesytem is mounted with MS_NOEXEC + } + + // Test some combinations of mask. + assertAccess(file, R_OK, canRead); + assertAccess(file, W_OK, canWrite); + assertAccess(file, R_OK | W_OK, canRead && canWrite); + assertAccess(file, W_OK | F_OK, canWrite); + + if (checkExists) { + assertAccess(file, F_OK, exists); + } + } + + private static void assertAccess(File file, int mask, boolean expected) throws Exception { + if (expected) { + assertThat(Os.access(file.getAbsolutePath(), mask)).isTrue(); + } else { + assertThrows(ErrnoException.class, () -> { + Os.access(file.getAbsolutePath(), mask); + }); + } + } + + /** + * Creates a file at any location on storage (except external app data directory). + * The owner of the file is not the caller app. + */ + private void createFileAsLegacyApp(File file) throws Exception { + // Use a legacy app to create this file, since it could be outside shared storage. + Log.d(TAG, "Creating file " + file); + assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath())).isTrue(); + } + + /** + * Creates a file at any location on storage (except external app data directory). + * The owner of the file is not the caller app. + */ + private void createDirectoryAsLegacyApp(File file) throws Exception { + // Use a legacy app to create this file, since it could be outside shared storage. + Log.d(TAG, "Creating directory " + file); + // Create a tmp file in the target directory, this would also create the required + // directory, then delete the tmp file. It would leave only new directory. + assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue(); + assertThat(deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue(); + } + + /** + * Deletes a file or directory at any location on storage (except external app data directory). + */ + private void deleteAsLegacyApp(File file) throws Exception { + // Use a legacy app to delete this file, since it could be outside shared storage. + Log.d(TAG, "Deleting file " + file); + deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath()); + } + + /** + * Deletes the given file/directory recursively. If the file is a directory, then deletes all + * of its children (files or directories) recursively. + */ + private void deleteRecursivelyAsLegacyApp(File dir) throws Exception { + // Use a legacy app to delete this directory, since it could be outside shared storage. + Log.d(TAG, "Deleting directory " + dir); + deleteRecursivelyAs(APP_D_LEGACY_HAS_RW, dir.getAbsolutePath()); + } +} diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageBaseDeviceTest.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/ScopedStorageBaseDeviceTest.java index 95ea734f39d..92b9ee64e07 100644 --- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageBaseDeviceTest.java +++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/ScopedStorageBaseDeviceTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.scopedstorage.cts.device; +package android.scopedstorage.cts.lib; import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir; import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState; @@ -28,14 +28,13 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import android.provider.MediaStore; -import android.scopedstorage.cts.lib.TestUtils; import static org.junit.Assume.assumeTrue; import java.util.Arrays; import java.util.List; -class ScopedStorageBaseDeviceTest { +public class ScopedStorageBaseDeviceTest { private static final String VOLUME_PUBLIC = "volume_public"; private static void createPublicVolume() throws Exception { @@ -51,7 +50,7 @@ class ScopedStorageBaseDeviceTest { } } - void setupExternalStorage(String volumeName) throws Exception { + public void setupExternalStorage(String volumeName) throws Exception { assertThat(volumeName).isNotNull(); if (volumeName.equals(MediaStore.VOLUME_EXTERNAL)) { @@ -74,7 +73,7 @@ class ScopedStorageBaseDeviceTest { setupDefaultDirectories(); } - static List<String> getTestParameters() { + public static List<String> getTestParameters() { return Arrays.asList( MediaStore.VOLUME_EXTERNAL, VOLUME_PUBLIC diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java index a824a1558aa..3db6007cf43 100644 --- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java +++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java @@ -756,12 +756,7 @@ public class TestUtils { Install.single(testApp).commit(); assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1); if (grantStoragePermission) { - grantPermission(packageName, Manifest.permission.READ_EXTERNAL_STORAGE); - if (SdkLevel.isAtLeastT()) { - grantPermission(packageName, Manifest.permission.READ_MEDIA_IMAGES); - grantPermission(packageName, Manifest.permission.READ_MEDIA_AUDIO); - grantPermission(packageName, Manifest.permission.READ_MEDIA_VIDEO); - } + addressStoragePermissions(packageName, true); } Log.d(TAG, String.format("Successfully installed %s app", testApp.getPackageName())); } finally { @@ -769,6 +764,27 @@ public class TestUtils { } } + /** + * Grants or revokes storage read permissions. + */ + public static void addressStoragePermissions(String packageName, boolean grantPermission) { + if (grantPermission) { + grantPermission(packageName, Manifest.permission.READ_EXTERNAL_STORAGE); + if (SdkLevel.isAtLeastT()) { + grantPermission(packageName, Manifest.permission.READ_MEDIA_IMAGES); + grantPermission(packageName, Manifest.permission.READ_MEDIA_AUDIO); + grantPermission(packageName, Manifest.permission.READ_MEDIA_VIDEO); + } + } else { + revokePermission(packageName, Manifest.permission.READ_EXTERNAL_STORAGE); + if (SdkLevel.isAtLeastT()) { + revokePermission(packageName, Manifest.permission.READ_MEDIA_IMAGES); + revokePermission(packageName, Manifest.permission.READ_MEDIA_AUDIO); + revokePermission(packageName, Manifest.permission.READ_MEDIA_VIDEO); + } + } + } + public static boolean isAppInstalled(TestApp testApp) { boolean isAppInstalled = InstallUtils.getInstalledVersion(testApp.getPackageName()) != -1; diff --git a/hostsidetests/scopedstorage/redacturi/AndroidManifest.xml b/hostsidetests/scopedstorage/redacturi/AndroidManifest.xml new file mode 100644 index 00000000000..96d792e6116 --- /dev/null +++ b/hostsidetests/scopedstorage/redacturi/AndroidManifest.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.scopedstorage.cts.redacturi"> + + <uses-sdk android:minSdkVersion="29" /> + + <queries> + <package android:name="android.scopedstorage.cts.testapp.B.noperms" /> + <package android:name="android.scopedstorage.cts.testapp.E" /> + </queries> + + <application android:label="Scoped Storage Device Redact Uri Tests"> + <uses-library android:name="android.test.runner" /> + <activity android:name="android.scopedstorage.cts.lib.GetResultActivity"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.scopedstorage.cts.redacturi" + android:label="Device-only scoped storage redact uri tests" /> + +</manifest> diff --git a/hostsidetests/scopedstorage/redacturi/AndroidTest.xml b/hostsidetests/scopedstorage/redacturi/AndroidTest.xml new file mode 100644 index 00000000000..53722507b77 --- /dev/null +++ b/hostsidetests/scopedstorage/redacturi/AndroidTest.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Runs device-only tests for scoped storage"> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="CtsScopedStorageRedactUriTest.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" /> + <option name="test-file-name" value="CtsScopedStorageTestAppE.apk" /> + </target_preparer> + + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + + <option name="config-descriptor:metadata" key="component" value="framework" /> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" /> + <option name="test-suite-tag" value="cts" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.scopedstorage.cts.redacturi" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> + + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.mediaprovider" /> + </object> +</configuration> diff --git a/hostsidetests/scopedstorage/redacturi/src/android/scopedstorage/cts/redacturi/RedactUriDeviceTest.java b/hostsidetests/scopedstorage/redacturi/src/android/scopedstorage/cts/redacturi/RedactUriDeviceTest.java new file mode 100644 index 00000000000..b228dfe7e7b --- /dev/null +++ b/hostsidetests/scopedstorage/redacturi/src/android/scopedstorage/cts/redacturi/RedactUriDeviceTest.java @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.scopedstorage.cts.redacturi; + + +import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; +import static android.database.Cursor.FIELD_TYPE_BLOB; +import static android.scopedstorage.cts.lib.TestUtils.addressStoragePermissions; +import static android.scopedstorage.cts.lib.TestUtils.assertThrows; +import static android.scopedstorage.cts.lib.TestUtils.canOpenRedactedUriForWrite; +import static android.scopedstorage.cts.lib.TestUtils.canQueryOnUri; +import static android.scopedstorage.cts.lib.TestUtils.checkPermission; +import static android.scopedstorage.cts.lib.TestUtils.forceStopApp; +import static android.scopedstorage.cts.lib.TestUtils.getContentResolver; +import static android.scopedstorage.cts.lib.TestUtils.grantPermission; +import static android.scopedstorage.cts.lib.TestUtils.isFileDescriptorRedacted; +import static android.scopedstorage.cts.lib.TestUtils.isFileOpenRedacted; +import static android.scopedstorage.cts.lib.TestUtils.setShouldForceStopTestApp; + +import static androidx.test.InstrumentationRegistry.getContext; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.Manifest; +import android.content.ContentValues; +import android.content.Intent; +import android.database.Cursor; +import android.media.ExifInterface; +import android.net.Uri; +import android.scopedstorage.cts.lib.ScopedStorageBaseDeviceTest; +import android.os.Bundle; +import android.os.Environment; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor; +import android.provider.MediaStore; +import android.system.Os; + +import androidx.test.filters.SdkSuppress; + +import com.android.cts.install.lib.TestApp; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * Device-side test suite to verify redacted URI operations. + */ +@RunWith(Parameterized.class) +@SdkSuppress(minSdkVersion = 31, codeName = "S") +public class RedactUriDeviceTest extends ScopedStorageBaseDeviceTest { + + /** + * To help avoid flaky tests, give ourselves a unique nonce to be used for + * all filesystem paths, so that we don't risk conflicting with previous + * test runs. + */ + static final String NONCE = String.valueOf(System.nanoTime()); + + static final String IMAGE_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".jpg"; + + static final String FUZZER_HEIC_FILE_NAME = + "ScopedStorageDeviceTest_file_fuzzer_" + NONCE + ".heic"; + + // An app with no permissions + private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB", + "android.scopedstorage.cts.testapp.B.noperms", 1, false, + "CtsScopedStorageTestAppB.apk"); + + private static final TestApp APP_E = new TestApp("TestAppE", + "android.scopedstorage.cts.testapp.E", 1, false, "CtsScopedStorageTestAppE.apk"); + + @Parameterized.Parameter(0) + public String mVolumeName; + + /** Parameters data. */ + @Parameterized.Parameters(name = "volume={0}") + public static Iterable<? extends Object> data() { + return getTestParameters(); + } + + @BeforeClass + public static void setupApps() { + // Installed by target preparer + assertThat(checkPermission(APP_B_NO_PERMS, + Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse(); + setShouldForceStopTestApp(false); + } + + @AfterClass + public static void destroy() { + setShouldForceStopTestApp(true); + } + + @Test + public void testRedactedUri_single() throws Exception { + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + + try { + final Uri uri = MediaStore.scanFile(getContentResolver(), img); + final Uri redactedUri = MediaStore.getRedactedUri(getContentResolver(), uri); + testRedactedUriCommon(uri, redactedUri); + } finally { + img.delete(); + } + } + + @Test + public void testRedactedUri_list() throws Exception { + List<Uri> uris = new ArrayList<>(); + List<File> files = new ArrayList<>(); + + try { + for (int i = 0; i < 10; i++) { + File file = stageImageFileWithMetadata("img_metadata" + String.valueOf( + System.nanoTime()) + i + ".jpg"); + files.add(file); + uris.add(MediaStore.scanFile(getContentResolver(), file)); + } + + final Collection<Uri> redactedUris = MediaStore.getRedactedUri(getContentResolver(), + uris); + int i = 0; + for (Uri redactedUri : redactedUris) { + Uri uri = uris.get(i++); + testRedactedUriCommon(uri, redactedUri); + } + } finally { + files.forEach(file -> file.delete()); + } + } + + @Test + public void testQueryOnRedactionUri() throws Exception { + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + final Uri uri = MediaStore.scanFile(getContentResolver(), img); + final Uri redactedUri = MediaStore.getRedactedUri(getContentResolver(), uri); + final Cursor uriCursor = getContentResolver().query(uri, null, null, null); + final String redactedUriDir = ".transforms/synthetic/redacted"; + final String redactedUriDirAbsolutePath = + Environment.getExternalStorageDirectory() + "/" + redactedUriDir; + try { + assertNotNull(uriCursor); + assertThat(uriCursor.moveToFirst()).isTrue(); + + final Cursor redactedUriCursor = getContentResolver().query(redactedUri, null, null, + null); + assertNotNull(redactedUriCursor); + assertThat(redactedUriCursor.moveToFirst()).isTrue(); + + assertEquals(redactedUriCursor.getColumnCount(), uriCursor.getColumnCount()); + + final String data = getStringFromCursor(redactedUriCursor, + MediaStore.MediaColumns.DATA); + final String redactedUriDisplayName = redactedUri.getLastPathSegment() + ".jpg"; + assertEquals(redactedUriDirAbsolutePath + "/" + redactedUriDisplayName, data); + + final String name = getStringFromCursor(redactedUriCursor, + MediaStore.MediaColumns.DISPLAY_NAME); + assertEquals(redactedUriDisplayName, name); + + final String relativePath = getStringFromCursor(redactedUriCursor, + MediaStore.MediaColumns.RELATIVE_PATH); + assertEquals(redactedUriDir, relativePath); + + final String bucketDisplayName = getStringFromCursor(redactedUriCursor, + MediaStore.MediaColumns.BUCKET_DISPLAY_NAME); + assertEquals(redactedUriDir, bucketDisplayName); + + final String docId = getStringFromCursor(redactedUriCursor, + MediaStore.MediaColumns.DOCUMENT_ID); + assertNull(docId); + + final String insId = getStringFromCursor(redactedUriCursor, + MediaStore.MediaColumns.INSTANCE_ID); + assertNull(insId); + + final String bucId = getStringFromCursor(redactedUriCursor, + MediaStore.MediaColumns.BUCKET_ID); + assertNull(bucId); + + final Collection<String> updatedCols = Arrays.asList(MediaStore.MediaColumns._ID, + MediaStore.MediaColumns.DISPLAY_NAME, + MediaStore.MediaColumns.RELATIVE_PATH, + MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, + MediaStore.MediaColumns.DATA, + MediaStore.MediaColumns.DOCUMENT_ID, + MediaStore.MediaColumns.INSTANCE_ID, + MediaStore.MediaColumns.BUCKET_ID); + for (String colName : uriCursor.getColumnNames()) { + if (!updatedCols.contains(colName)) { + if (uriCursor.getType(uriCursor.getColumnIndex(colName)) == FIELD_TYPE_BLOB) { + assertThat( + Arrays.equals(uriCursor.getBlob(uriCursor.getColumnIndex(colName)), + redactedUriCursor.getBlob(redactedUriCursor.getColumnIndex( + colName)))).isTrue(); + } else { + assertEquals(getStringFromCursor(uriCursor, colName), + getStringFromCursor(redactedUriCursor, colName)); + } + } + } + } finally { + img.delete(); + } + } + + /* + * Verify that app can't open the shared redacted URI for write. + **/ + @Test + public void testSharedRedactedUri_openFdForWrite() throws Exception { + forceStopApp(APP_B_NO_PERMS.getPackageName()); + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + try { + Uri redactedUri = shareAndGetRedactedUri(img, APP_B_NO_PERMS); + assertThrows(UnsupportedOperationException.class, + () -> canOpenRedactedUriForWrite(APP_B_NO_PERMS, redactedUri)); + } finally { + img.delete(); + } + } + + /* + * Verify that app with correct permission can open the shared redacted URI for read in + * redacted mode. + **/ + @Test + public void testSharedRedactedUri_openFdForRead() throws Exception { + forceStopApp(APP_B_NO_PERMS.getPackageName()); + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + try { + final Uri redactedUri = shareAndGetRedactedUri(img, APP_B_NO_PERMS); + assertThat(isFileDescriptorRedacted(APP_B_NO_PERMS, redactedUri)).isTrue(); + } finally { + img.delete(); + } + } + + /* + * Verify that app with correct permission can open the shared redacted URI for read in + * redacted mode. + **/ + @Test + public void testSharedRedactedUri_openFileForRead() throws Exception { + forceStopApp(APP_B_NO_PERMS.getPackageName()); + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + try { + Uri redactedUri = shareAndGetRedactedUri(img, APP_B_NO_PERMS); + assertThat(isFileOpenRedacted(APP_B_NO_PERMS, redactedUri)).isTrue(); + } finally { + img.delete(); + } + } + + /* + * Verify that the app with redacted URI granted can query it. + **/ + @Test + public void testSharedRedactedUri_query() throws Exception { + forceStopApp(APP_B_NO_PERMS.getPackageName()); + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + try { + Uri redactedUri = shareAndGetRedactedUri(img, APP_B_NO_PERMS); + assertThat(canQueryOnUri(APP_B_NO_PERMS, redactedUri)).isTrue(); + } finally { + img.delete(); + } + } + + /* + * Verify that for app with AML permission shared redacted URI opens for read in redacted mode. + **/ + @Test + public void testSharedRedactedUri_openFileForRead_withLocationPerm() throws Exception { + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + forceStopApp(APP_E.getPackageName()); + try { + addressStoragePermissions(APP_E.getPackageName(), true); + grantPermission(APP_E.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION); + + Uri redactedUri = shareAndGetRedactedUri(img, APP_E); + assertThat(isFileOpenRedacted(APP_E, redactedUri)).isTrue(); + } finally { + img.delete(); + } + } + + /* + * Verify that for app with AML permission shared redacted URI opens for read in redacted mode. + **/ + @Test + public void testSharedRedactedUri_openFdForRead_withLocationPerm() throws Exception { + forceStopApp(APP_E.getPackageName()); + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + try { + addressStoragePermissions(APP_E.getPackageName(), true); + grantPermission(APP_E.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION); + + Uri redactedUri = shareAndGetRedactedUri(img, APP_E); + assertThat(isFileDescriptorRedacted(APP_E, redactedUri)).isTrue(); + } finally { + img.delete(); + } + } + + /* + * Verify that the test app can't access unshared redacted uri via file descriptor + **/ + @Test + public void testUnsharedRedactedUri_openFdForRead() throws Exception { + forceStopApp(APP_B_NO_PERMS.getPackageName()); + forceStopApp(APP_E.getPackageName()); + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + try { + addressStoragePermissions(APP_E.getPackageName(), true); + + final Uri redactedUri = getRedactedUri(img); + // APP_E has R_E_S, so should have access to redactedUri + assertThat(isFileDescriptorRedacted(APP_E, redactedUri)).isTrue(); + assertThrows(SecurityException.class, + () -> isFileDescriptorRedacted(APP_B_NO_PERMS, redactedUri)); + } finally { + img.delete(); + } + } + + /* + * Verify that the test app can't access unshared redacted uri via file path + **/ + @Test + public void testUnsharedRedactedUri_openFileForRead() throws Exception { + forceStopApp(APP_B_NO_PERMS.getPackageName()); + forceStopApp(APP_E.getPackageName()); + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + try { + addressStoragePermissions(APP_E.getPackageName(), true); + + final Uri redactedUri = getRedactedUri(img); + // APP_E has R_E_S + assertThat(isFileOpenRedacted(APP_E, redactedUri)).isTrue(); + assertThrows(IOException.class, () -> isFileOpenRedacted(APP_B_NO_PERMS, redactedUri)); + } finally { + img.delete(); + } + } + + @Test + public void testGrantUriPermissionsForRedactedUri() throws Exception { + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + final Uri redactedUri = getRedactedUri(img); + try { + getContext().grantUriPermission(APP_B_NO_PERMS.getPackageName(), redactedUri, + FLAG_GRANT_READ_URI_PERMISSION); + assertThrows(SecurityException.class, () -> + getContext().grantUriPermission(APP_B_NO_PERMS.getPackageName(), redactedUri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION)); + } finally { + img.delete(); + } + } + + @Test + public void testDisallowedOperationsOnRedactedUri() throws Exception { + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + final Uri redactedUri = getRedactedUri(img); + try { + ContentValues cv = new ContentValues(); + cv.put(MediaStore.MediaColumns.DATE_ADDED, 1); + assertEquals(0, getContentResolver().update(redactedUri, new ContentValues(), + new Bundle())); + assertEquals(0, getContentResolver().delete(redactedUri, new Bundle())); + } finally { + img.delete(); + } + } + + @Test + public void testOpenOnRedactedUri_file() throws Exception { + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + final Uri redactedUri = getRedactedUri(img); + try { + assertUriIsUnredacted(img); + + final Cursor redactedUriCursor = getRedactedCursor(redactedUri); + File file = new File( + getStringFromCursor(redactedUriCursor, MediaStore.MediaColumns.DATA)); + ExifInterface redactedExifInf = new ExifInterface(file); + assertUriIsRedacted(redactedExifInf); + + assertThrows(FileNotFoundException.class, () -> new FileOutputStream(file)); + } finally { + img.delete(); + } + } + + @Test + public void testOpenOnRedactedUri_write() throws Exception { + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + final Uri redactedUri = getRedactedUri(img); + try { + assertThrows(UnsupportedOperationException.class, + () -> getContentResolver().openFileDescriptor(redactedUri, + "w")); + } finally { + img.delete(); + } + } + + @Test + public void testOpenOnRedactedUri_inputstream() throws Exception { + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + final Uri redactedUri = getRedactedUri(img); + try { + assertUriIsUnredacted(img); + + try (InputStream is = getContentResolver().openInputStream(redactedUri)) { + ExifInterface redactedExifInf = new ExifInterface(is); + assertUriIsRedacted(redactedExifInf); + } + } finally { + img.delete(); + } + } + + @Test + public void testOpenOnRedactedUri_read() throws Exception { + final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME); + final Uri redactedUri = getRedactedUri(img); + try { + assertUriIsUnredacted(img); + + try (ParcelFileDescriptor pfd = + getContentResolver().openFileDescriptor(redactedUri, "r")) { + FileDescriptor fd = pfd.getFileDescriptor(); + ExifInterface redactedExifInf = new ExifInterface(fd); + assertUriIsRedacted(redactedExifInf); + } + } finally { + img.delete(); + } + } + + @Test + public void testOpenOnRedactedUri_readFuzzer() throws Exception { + final File img = stageFuzzerImageFileWithMetadata(FUZZER_HEIC_FILE_NAME); + final Uri redactedUri = getRedactedUri(img); + try { + assertUriIsUnredacted(img); + + try (ParcelFileDescriptor pfd = + getContentResolver().openFileDescriptor(redactedUri, "r")) { + FileDescriptor fd = pfd.getFileDescriptor(); + int bufSize = 0x1000000; + byte[] data = new byte[bufSize]; + int fileSize = Os.read(fd, data, 0, bufSize); + assertUriIsRedacted(data, fileSize); + } + } finally { + img.delete(); + } + } + + private void testRedactedUriCommon(Uri uri, Uri redactedUri) { + assertEquals(redactedUri.getAuthority(), uri.getAuthority()); + assertEquals(redactedUri.getScheme(), uri.getScheme()); + assertNotEquals(redactedUri.getPath(), uri.getPath()); + assertNotEquals(redactedUri.getPathSegments(), uri.getPathSegments()); + + final String uriId = redactedUri.getLastPathSegment(); + assertThat(uriId.startsWith("RUID")).isTrue(); + assertEquals(uriId.length(), 36); + } + + private Uri shareAndGetRedactedUri(File file, TestApp testApp) { + final Uri redactedUri = getRedactedUri(file); + getContext().grantUriPermission(testApp.getPackageName(), redactedUri, + FLAG_GRANT_READ_URI_PERMISSION); + + return redactedUri; + } + + private Uri getRedactedUri(File file) { + final Uri uri = MediaStore.scanFile(getContentResolver(), file); + return MediaStore.getRedactedUri(getContentResolver(), uri); + } + + private void assertUriIsUnredacted(File img) throws Exception { + final ExifInterface exifInterface = new ExifInterface(img); + assertNotEquals(exifInterface.getGpsDateTime(), -1); + + float[] latLong = new float[]{0, 0}; + exifInterface.getLatLong(latLong); + assertNotEquals(latLong[0], 0); + assertNotEquals(latLong[1], 0); + } + + private void assertUriIsRedacted(ExifInterface redactedExifInf) { + assertEquals(redactedExifInf.getGpsDateTime(), -1); + float[] latLong = new float[]{0, 0}; + redactedExifInf.getLatLong(latLong); + assertEquals(latLong[0], 0.0, 0.0); + assertEquals(latLong[1], 0.0, 0.0); + } + + private void assertUriIsRedacted(byte[] data, int fileSize) { + // Data in redaction ranges should be zero. + int[] start = new int[]{50538, 712941, 712965, 712989, 713033, 713101}; + int[] end = new int[]{711958, 712943, 712967, 712990, 713100, 713125}; + + assertTrue(fileSize == 4407744); + for (int index = 0; index < start.length && index < end.length; index++) { + for (int c = start[index]; c < end[index]; c++) { + assertTrue("It should be zero!", data[c] == (byte) 0); + } + } + } + + private Cursor getRedactedCursor(Uri redactedUri) { + Cursor redactedUriCursor = getContentResolver().query(redactedUri, null, null, null); + assertNotNull(redactedUriCursor); + assertThat(redactedUriCursor.moveToFirst()).isTrue(); + + return redactedUriCursor; + } + + private String getStringFromCursor(Cursor c, String colName) { + return c.getString(c.getColumnIndex(colName)); + } + + private File stageImageFileWithMetadata(String name) throws Exception { + return stageImageFileWithMetadata(name, R.raw.img_with_metadata); + } + + private File stageFuzzerImageFileWithMetadata(String name) throws Exception { + return stageImageFileWithMetadata(name, R.raw.fuzzer); + } + + private File stageImageFileWithMetadata(String name, int sourceId) throws Exception { + final File img = new File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), name); + + try (InputStream in = + getContext().getResources().openRawResource(sourceId); + OutputStream out = new FileOutputStream(img)) { + // Dump the image we have to external storage + FileUtils.copy(in, out); + } + + return img; + } +}
\ No newline at end of file diff --git a/tests/PhotoPicker/src/android/photopicker/cts/ActionPickImagesOnlyTest.java b/tests/PhotoPicker/src/android/photopicker/cts/ActionPickImagesOnlyTest.java index c731e399a00..b6180df5968 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/ActionPickImagesOnlyTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/ActionPickImagesOnlyTest.java @@ -18,6 +18,7 @@ package android.photopicker.cts; import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImagesAndGetUris; import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia; +import static android.photopicker.cts.util.PhotoPickerFilesUtils.getMediaId; import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT; import static android.photopicker.cts.util.PhotoPickerUiUtils.clickAndWait; import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton; @@ -141,6 +142,34 @@ public class ActionPickImagesOnlyTest extends PhotoPickerBaseTest { final int count = clipData.getItemCount(); assertThat(count).isEqualTo(maxCount); } + @Test + public void testMultiSelectOrdered() throws Exception { + final int imageCount = 4; + List<Uri> createdImages = createImagesAndGetUris(imageCount, mContext.getUserId()); + mUriList.addAll(createdImages); + Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + addOrderedSelectionFlag(intent); + launchPhotoPickerForIntent(intent); + + final List<UiObject> itemList = findItemList(imageCount); + final int itemCount = itemList.size(); + assertThat(itemCount).isEqualTo(imageCount); + for (int i = 0; i < itemCount; i++) { + clickAndWait(sDevice, itemList.get(i)); + } + + clickAndWait(sDevice, findAddButton()); + + final ClipData clipData = mActivity.getResult().data.getClipData(); + final int count = clipData.getItemCount(); + assertThat(count).isEqualTo(itemCount); + for (int i = 0; i < count; i++) { + final Uri imageSelectedUri = clipData.getItemAt(i).getUri(); + // The last image created, is the first on the photopicker grid. + final Uri imageCreatedUri = createdImages.get(count - i - 1); + assertThat(getMediaId(imageSelectedUri)).isEqualTo(getMediaId(imageCreatedUri)); + } + } @Test public void testDoesNotRespectExtraAllowMultiple() throws Exception { @@ -178,4 +207,18 @@ public class ActionPickImagesOnlyTest extends PhotoPickerBaseTest { final GetResultActivity.Result res = mActivity.getResult(); assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED); } + private void addOrderedSelectionFlag(Intent intent) { + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit()); + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_IN_ORDER, true); + } + + private void launchPhotoPickerForIntent(Intent intent) throws Exception { + // GET_CONTENT needs to have setType + if (Intent.ACTION_GET_CONTENT.equals(intent.getAction()) && intent.getType() == null) { + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"}); + } + + mActivity.startActivityForResult(intent, REQUEST_CODE); + } } diff --git a/tests/PhotoPicker/src/android/photopicker/cts/ActionUserSelectImagesForAppTest.java b/tests/PhotoPicker/src/android/photopicker/cts/ActionUserSelectImagesForAppTest.java index 198b9e83c0c..05616ccdf59 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/ActionUserSelectImagesForAppTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/ActionUserSelectImagesForAppTest.java @@ -17,13 +17,11 @@ package android.photopicker.cts; import static android.photopicker.cts.PhotoPickerCloudUtils.containsExcept; -import static android.photopicker.cts.PhotoPickerCloudUtils.disableCloudMediaAndClearAllowedCloudProviders; +import static android.photopicker.cts.PhotoPickerCloudUtils.disableDeviceConfigSync; import static android.photopicker.cts.PhotoPickerCloudUtils.enableCloudMediaAndSetAllowedCloudProviders; import static android.photopicker.cts.PhotoPickerCloudUtils.extractMediaIds; import static android.photopicker.cts.PhotoPickerCloudUtils.fetchPickerMedia; -import static android.photopicker.cts.PhotoPickerCloudUtils.getAllowedProvidersDeviceConfig; import static android.photopicker.cts.PhotoPickerCloudUtils.initCloudProviderWithImage; -import static android.photopicker.cts.PhotoPickerCloudUtils.isCloudMediaEnabled; import static android.photopicker.cts.PhotoPickerCloudUtils.selectAndAddPickerMedia; import static android.photopicker.cts.PickerProviderMediaGenerator.getMediaGenerator; import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImagesAndGetUris; @@ -48,7 +46,6 @@ import android.photopicker.cts.cloudproviders.CloudProviderPrimary; import android.photopicker.cts.util.PhotoPickerFilesUtils; import android.photopicker.cts.util.UiAssertionUtils; import android.provider.MediaStore; -import android.util.Log; import android.util.Pair; import androidx.annotation.Nullable; @@ -70,44 +67,24 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class ActionUserSelectImagesForAppTest extends PhotoPickerBaseTest { private static final String TAG = ActionUserSelectImagesForAppTest.class.getSimpleName(); - private static boolean sCloudMediaPreviouslyEnabled; @Nullable - private static String sPreviouslyAllowedCloudProviders; - @Nullable - private static String sPreviouslySetCloudProvider; + private static DeviceStatePreserver sDeviceStatePreserver; @BeforeClass public static void setUpClass() throws IOException { - // Store the current Cloud-Media feature configs which we will override during the test, - // and will need to restore after the test finished. - sCloudMediaPreviouslyEnabled = isCloudMediaEnabled(); - if (sCloudMediaPreviouslyEnabled) { - sPreviouslyAllowedCloudProviders = getAllowedProvidersDeviceConfig(); - } - - try { - sPreviouslySetCloudProvider = getCurrentCloudProvider(); - } catch (RuntimeException e) { - Log.e(TAG, "Could not get previously set cloud provider", e); - sPreviouslySetCloudProvider = INVALID_CLOUD_PROVIDER; - } + sDeviceStatePreserver = new DeviceStatePreserver(sDevice); + sDeviceStatePreserver.saveCurrentCloudProviderState(); + disableDeviceConfigSync(); // Override the allowed cloud providers config to enable the banners // (this is a self-instrumenting test, so "target" package name and "own" package name are // same: android.photopicker.cts). enableCloudMediaAndSetAllowedCloudProviders(sTargetPackageName); - } @AfterClass public static void tearDownClass() throws Exception { - // Restore Cloud-Media feature configs. - if (sCloudMediaPreviouslyEnabled) { - enableCloudMediaAndSetAllowedCloudProviders(sPreviouslyAllowedCloudProviders); - } else { - disableCloudMediaAndClearAllowedCloudProviders(); - } - setCloudProvider(sPreviouslySetCloudProvider); + sDeviceStatePreserver.restoreCloudProviderState(); } @After diff --git a/tests/PhotoPicker/src/android/photopicker/cts/CloudPhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/CloudPhotoPickerTest.java index 27cd7fde595..f2e3c684d60 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/CloudPhotoPickerTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/CloudPhotoPickerTest.java @@ -18,11 +18,9 @@ package android.photopicker.cts; import static android.photopicker.cts.PhotoPickerCloudUtils.addImage; import static android.photopicker.cts.PhotoPickerCloudUtils.containsExcept; -import static android.photopicker.cts.PhotoPickerCloudUtils.disableCloudMediaAndClearAllowedCloudProviders; +import static android.photopicker.cts.PhotoPickerCloudUtils.disableDeviceConfigSync; import static android.photopicker.cts.PhotoPickerCloudUtils.enableCloudMediaAndSetAllowedCloudProviders; import static android.photopicker.cts.PhotoPickerCloudUtils.extractMediaIds; -import static android.photopicker.cts.PhotoPickerCloudUtils.getAllowedProvidersDeviceConfig; -import static android.photopicker.cts.PhotoPickerCloudUtils.isCloudMediaEnabled; import static android.photopicker.cts.PickerProviderMediaGenerator.MediaGenerator; import static android.photopicker.cts.PickerProviderMediaGenerator.syncCloudProvider; import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImagesAndGetUris; @@ -46,7 +44,6 @@ import android.photopicker.cts.cloudproviders.CloudProviderNoPermission; import android.photopicker.cts.cloudproviders.CloudProviderPrimary; import android.photopicker.cts.cloudproviders.CloudProviderSecondary; import android.provider.MediaStore; -import android.util.Log; import android.util.Pair; import androidx.annotation.Nullable; @@ -85,25 +82,14 @@ public class CloudPhotoPickerTest extends PhotoPickerBaseTest { private static final String CLOUD_ID1 = "CLOUD_ID1"; private static final String CLOUD_ID2 = "CLOUD_ID2"; - private static boolean sCloudMediaPreviouslyEnabled; - private static String sPreviouslyAllowedCloudProviders; @Nullable - private static String sPreviouslySetCloudProvider; + private static DeviceStatePreserver sDeviceStatePreserver; @BeforeClass public static void setUpBeforeClass() throws IOException { - // Store the current CMP configs, so that we can reset them at the end of the test. - sCloudMediaPreviouslyEnabled = isCloudMediaEnabled(); - if (sCloudMediaPreviouslyEnabled) { - sPreviouslyAllowedCloudProviders = getAllowedProvidersDeviceConfig(); - } - - try { - sPreviouslySetCloudProvider = getCurrentCloudProvider(); - } catch (RuntimeException e) { - Log.e(TAG, "Could not get previously set cloud provider", e); - sPreviouslySetCloudProvider = INVALID_CLOUD_PROVIDER; - } + sDeviceStatePreserver = new DeviceStatePreserver(sDevice); + sDeviceStatePreserver.saveCurrentCloudProviderState(); + disableDeviceConfigSync(); // This is a self-instrumentation test, so both "target" package name and "own" package name // should be the same (android.photopicker.cts). @@ -112,13 +98,7 @@ public class CloudPhotoPickerTest extends PhotoPickerBaseTest { @AfterClass public static void tearDownClass() throws Exception { - // Reset CloudMedia configs. - if (sCloudMediaPreviouslyEnabled) { - enableCloudMediaAndSetAllowedCloudProviders(sPreviouslyAllowedCloudProviders); - } else { - disableCloudMediaAndClearAllowedCloudProviders(); - } - setCloudProvider(sPreviouslySetCloudProvider); + sDeviceStatePreserver.restoreCloudProviderState(); } @Before public void setUp() throws Exception { diff --git a/tests/PhotoPicker/src/android/photopicker/cts/DeviceStatePreserver.java b/tests/PhotoPicker/src/android/photopicker/cts/DeviceStatePreserver.java new file mode 100644 index 00000000000..3f660a41215 --- /dev/null +++ b/tests/PhotoPicker/src/android/photopicker/cts/DeviceStatePreserver.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.photopicker.cts; + +import static android.photopicker.cts.PhotoPickerBaseTest.INVALID_CLOUD_PROVIDER; +import static android.photopicker.cts.PhotoPickerCloudUtils.NAMESPACE_MEDIAPROVIDER; +import static android.photopicker.cts.PhotoPickerCloudUtils.NAMESPACE_STORAGE_NATIVE_BOOT; +import static android.photopicker.cts.PhotoPickerCloudUtils.disableCloudMediaAndClearAllowedCloudProviders; +import static android.photopicker.cts.PhotoPickerCloudUtils.enableCloudMediaAndSetAllowedCloudProviders; +import static android.photopicker.cts.PhotoPickerCloudUtils.getAllowedProvidersDeviceConfig; +import static android.photopicker.cts.PhotoPickerCloudUtils.getCurrentCloudProvider; +import static android.photopicker.cts.PhotoPickerCloudUtils.isCloudMediaEnabled; +import static android.photopicker.cts.PhotoPickerCloudUtils.setCloudProvider; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.uiautomator.UiDevice; + +import java.io.IOException; + +/** + * Provides helper functions to save current device state and restore the state after running tests. + */ +public class DeviceStatePreserver { + private static final String TAG = DeviceStatePreserver.class.getSimpleName(); + + private UiDevice mUiDevice; + + // Previous state in storage_native_boot namespace + private static boolean sIsCloudEnabledStorageNativeBoot; + @Nullable + private static String sAllowedCloudProvidersStorageNativeBoot; + + // Previous state in mediaprovider namespace + private static boolean sIsCloudEnabledMediaProvider; + @Nullable + private static String sAllowedCloudProvidersMediaProvider; + + @Nullable + private static String sPreviouslySetCloudProvider; + + public DeviceStatePreserver(@NonNull UiDevice device) { + mUiDevice = device; + } + + /** + * Saves current cloud provider related device config flags from + * {@link PhotoPickerCloudUtils#NAMESPACE_STORAGE_NATIVE_BOOT} and + * {@link PhotoPickerCloudUtils#NAMESPACE_MEDIAPROVIDER}. + * Also saves the current cloud provider. + */ + public void saveCurrentCloudProviderState() throws IOException { + // Save flags in storage_native_boot namespace + sIsCloudEnabledStorageNativeBoot = isCloudMediaEnabled(NAMESPACE_STORAGE_NATIVE_BOOT); + if (sIsCloudEnabledStorageNativeBoot) { + sAllowedCloudProvidersStorageNativeBoot = + getAllowedProvidersDeviceConfig(NAMESPACE_STORAGE_NATIVE_BOOT); + } + + // Save flags in mediaprovider namespace + sIsCloudEnabledMediaProvider = isCloudMediaEnabled(NAMESPACE_MEDIAPROVIDER); + if (sIsCloudEnabledMediaProvider) { + sAllowedCloudProvidersMediaProvider = + getAllowedProvidersDeviceConfig(NAMESPACE_MEDIAPROVIDER); + } + + // Save current cloud provider. + try { + sPreviouslySetCloudProvider = getCurrentCloudProvider(mUiDevice); + } catch (RuntimeException e) { + Log.e(TAG, "Could not get previously set cloud provider", e); + sPreviouslySetCloudProvider = INVALID_CLOUD_PROVIDER; + } + } + + /** + * Restores the current cloud provider state previously saved using + * {@link DeviceStatePreserver#saveCurrentCloudProviderState()}. + */ + public void restoreCloudProviderState() throws Exception { + // Restore flags in storage_native_boot namespace + if (sIsCloudEnabledStorageNativeBoot) { + enableCloudMediaAndSetAllowedCloudProviders( + NAMESPACE_STORAGE_NATIVE_BOOT, sAllowedCloudProvidersStorageNativeBoot); + } else { + disableCloudMediaAndClearAllowedCloudProviders(NAMESPACE_STORAGE_NATIVE_BOOT); + } + + // Restore flags in mediaprovider namespace + if (sIsCloudEnabledMediaProvider) { + enableCloudMediaAndSetAllowedCloudProviders( + NAMESPACE_MEDIAPROVIDER, sAllowedCloudProvidersMediaProvider); + } else { + disableCloudMediaAndClearAllowedCloudProviders(NAMESPACE_MEDIAPROVIDER); + } + + // Restore previously set cloud provider. + setCloudProvider(mUiDevice, sPreviouslySetCloudProvider); + } +} diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBannersTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBannersTest.java deleted file mode 100644 index 8832209b687..00000000000 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBannersTest.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.photopicker.cts; - -import static android.photopicker.cts.PhotoPickerCloudUtils.disableCloudMediaAndClearAllowedCloudProviders; -import static android.photopicker.cts.PhotoPickerCloudUtils.enableCloudMediaAndSetAllowedCloudProviders; -import static android.photopicker.cts.PhotoPickerCloudUtils.getAllowedProvidersDeviceConfig; -import static android.photopicker.cts.PhotoPickerCloudUtils.isCloudMediaEnabled; -import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImage; -import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia; -import static android.photopicker.cts.util.PhotoPickerUiUtils.TIMEOUT; -import static android.photopicker.cts.util.PhotoPickerUiUtils.clickAndWait; -import static android.photopicker.cts.util.PhotoPickerUiUtils.findBannerActionButton; -import static android.photopicker.cts.util.PhotoPickerUiUtils.findBannerDismissButton; -import static android.photopicker.cts.util.PhotoPickerUiUtils.findBannerPrimaryText; -import static android.photopicker.cts.util.PhotoPickerUiUtils.findSettingsOverflowMenuItem; -import static android.photopicker.cts.util.PhotoPickerUiUtils.getBannerPrimaryText; -import static android.photopicker.cts.util.PhotoPickerUiUtils.isPhotoPickerVisible; -import static android.photopicker.cts.util.PhotoPickerUiUtils.verifySettingsActivityIsVisible; -import static android.provider.MediaStore.ACTION_PICK_IMAGES; -import static android.provider.MediaStore.EXTRA_PICK_IMAGES_MAX; -import static android.provider.MediaStore.getPickImagesMaxLimit; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.photopicker.cts.cloudproviders.CloudProviderPrimary; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.test.filters.SdkSuppress; -import androidx.test.uiautomator.UiObject; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.IOException; - -/** - * Photo Picker Banner Tests for common flows. - */ -// TODO(b/195009187): Enabling the banners requires setting allowed_cloud_providers device config. -// We currently can't do this in R. -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) -public class PhotoPickerBannersTest extends PhotoPickerBaseTest { - private static final String TAG = PhotoPickerBannersTest.class.getSimpleName(); - private static boolean sCloudMediaPreviouslyEnabled; - private static String sPreviouslyAllowedCloudProviders; - @Nullable - private static String sPreviouslySetCloudProvider; - private Uri mLocalMediaFileUri; - - @BeforeClass - public static void setUpBeforeClass() throws IOException { - // Store the current CMP configs, so that we can reset them at the end of the test. - sCloudMediaPreviouslyEnabled = isCloudMediaEnabled(); - if (sCloudMediaPreviouslyEnabled) { - sPreviouslyAllowedCloudProviders = getAllowedProvidersDeviceConfig(); - } - - try { - sPreviouslySetCloudProvider = getCurrentCloudProvider(); - } catch (RuntimeException e) { - Log.e(TAG, "Could not get previously set cloud provider", e); - sPreviouslySetCloudProvider = INVALID_CLOUD_PROVIDER; - } - - // Override the allowed cloud providers config to enable the banners. - enableCloudMediaAndSetAllowedCloudProviders(sTargetPackageName); - } - - @AfterClass - public static void tearDownClass() throws Exception { - // Reset CloudMedia configs. - if (sCloudMediaPreviouslyEnabled) { - enableCloudMediaAndSetAllowedCloudProviders(sPreviouslyAllowedCloudProviders); - } else { - disableCloudMediaAndClearAllowedCloudProviders(); - } - setCloudProvider(sPreviouslySetCloudProvider); - } - - @Before - public void setUp() throws Exception { - super.setUp(); - setCloudProvider(/* authority */ null); - - // Create a local media file because if there's no media items for the picker grids, - // the recycler view gets hidden along with the banners. - mLocalMediaFileUri = createImage(mContext.getUserId(), /* isFavorite */ false).first; - } - - @After - public void tearDown() throws Exception { - if (!isHardwareSupported()) { - // No-op, skip tear down if hardware is not supported. - return; - } - - if (mActivity != null) { - mActivity.finish(); - } - - deleteMedia(mLocalMediaFileUri, mContext); - - setCloudProvider(/* authority */ null); - } - - @Test - @Ignore("Tracking this in b/274840171") - public void testChooseAppBannerOnDismiss() throws Exception { - // 1. Setting up the 'Choose App' banner. - setCloudMediaInfoForChooseAppBanner(); - - // 2. Assert that the 'Choose App' banner is visible. - assertThat(getBannerPrimaryText()).isEqualTo("Choose cloud media app"); - - // 3. Click the banner 'Dismiss' button. - final UiObject dismissButton = findBannerDismissButton(); - dismissButton.click(); - - // 4. Assert that the Banner disappeared while the Picker is still visible. - assertWithMessage("Timed out waiting for the banner to disappear") - .that(dismissButton.waitUntilGone(TIMEOUT)) - .isTrue(); - assertThatPhotoPickerActivityIsVisible(); - } - - @Test - @Ignore("Tracking this in b/274840171") - public void testChooseAppBannerOnActionButtonClick() throws Exception { - // 1. Setting up the 'Choose App' banner. - setCloudMediaInfoForChooseAppBanner(); - - // 2. Assert that the 'Choose App' banner is visible. - assertThat(getBannerPrimaryText()).isEqualTo("Choose cloud media app"); - - // 3. Click the banner 'Action' button. - findBannerActionButton().click(); - - // 4. Assert that Settings page is visible. - verifySettingsActivityIsVisible(); - sDevice.pressBack(); - } - - private void setCloudMediaInfoForChooseAppBanner() throws Exception { - // 1. Set a non-null cloud provider and launch the photo picker. - setCloudProvider(CloudProviderPrimary.AUTHORITY); - launchPickerActivity(); - - // 2. Wait for the banner controller construction. - findBannerPrimaryText().waitForExists(TIMEOUT); - - // 3. Set the cloud provider to None and reset the banner data in the same picker session. - - // 3a. Launch the settings activity. - final UiObject settingsMenuItem = findSettingsOverflowMenuItem(sDevice); - clickAndWait(sDevice, settingsMenuItem); - verifySettingsActivityIsVisible(); - - // 3b. Set the cloud provider to None. - setCloudProvider(/* authority */ null); - - // 3c. Go back to the picker. - sDevice.pressBack(); - assertThatPhotoPickerActivityIsVisible(); - } - - private void launchPickerActivity() { - final Intent intent = new Intent(ACTION_PICK_IMAGES); - intent.putExtra(EXTRA_PICK_IMAGES_MAX, getPickImagesMaxLimit()); - mActivity.startActivity(intent); - assertThatPhotoPickerActivityIsVisible(); - } - - private void assertThatPhotoPickerActivityIsVisible() { - assertWithMessage("Timed out waiting for the photo picker activity to appear") - .that(isPhotoPickerVisible()) - .isTrue(); - } -} diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java index d8d437e51d7..5d7f9cd6e43 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java @@ -22,9 +22,6 @@ import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.os.UserHandle; -import android.text.TextUtils; -import android.util.Log; import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; @@ -92,47 +89,11 @@ public class PhotoPickerBaseTest { } protected static void setCloudProvider(@Nullable String authority) throws Exception { - if (INVALID_CLOUD_PROVIDER.equals(authority)) { - Log.w(TAG, "Cloud provider is invalid. " - + "Ignoring the request to set the cloud provider to invalid"); - return; - } - if (authority == null) { - sDevice.executeShellCommand( - "content call" - + " --user " + UserHandle.myUserId() - + " --uri content://media/ --method set_cloud_provider --extra" - + " cloud_provider:n:null"); - } else { - sDevice.executeShellCommand( - "content call" - + " --user " + UserHandle.myUserId() - + " --uri content://media/ --method set_cloud_provider --extra" - + " cloud_provider:s:" - + authority); - } + PhotoPickerCloudUtils.setCloudProvider(sDevice, authority); } protected static String getCurrentCloudProvider() throws IOException { - final String out = - sDevice.executeShellCommand( - "content call" - + " --user " + UserHandle.myUserId() - + " --uri content://media/ --method get_cloud_provider"); - return extractCloudProvider(out); - } - - private static String extractCloudProvider(String out) { - String[] splitOutput; - if (TextUtils.isEmpty(out) || ((splitOutput = out.split("=")).length < 2)) { - throw new RuntimeException("Could not get current cloud provider. Output: " + out); - } - String cloudprovider = splitOutput[1]; - cloudprovider = cloudprovider.substring(0, cloudprovider.length() - 3); - if (cloudprovider.equals("null")) { - return null; - } - return cloudprovider; + return PhotoPickerCloudUtils.getCurrentCloudProvider(sDevice); } } diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCloudUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCloudUtils.java index cac90216423..e9a016df81a 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCloudUtils.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCloudUtils.java @@ -18,6 +18,7 @@ package android.photopicker.cts; import static android.Manifest.permission.READ_DEVICE_CONFIG; import static android.Manifest.permission.WRITE_DEVICE_CONFIG; +import static android.photopicker.cts.PhotoPickerBaseTest.INVALID_CLOUD_PROVIDER; import static android.photopicker.cts.PickerProviderMediaGenerator.syncCloudProvider; import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton; import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList; @@ -28,8 +29,10 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.app.UiAutomation; import android.content.ClipData; import android.content.Context; +import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.MediaStore; +import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -47,7 +50,8 @@ import java.util.Collections; import java.util.List; public class PhotoPickerCloudUtils { - private static final String NAMESPACE_MEDIAPROVIDER = "mediaprovider"; + public static final String NAMESPACE_MEDIAPROVIDER = "mediaprovider"; + public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot"; private static final String KEY_ALLOWED_CLOUD_PROVIDERS = "allowed_cloud_providers"; private static final String KEY_CLOUD_MEDIA_FEATURE_ENABLED = "cloud_media_feature_enabled"; private static final String DISABLE_DEVICE_CONFIG_SYNC = @@ -133,37 +137,61 @@ public class PhotoPickerCloudUtils { assertThat(mediaIds).containsNoneIn(Collections.singletonList(notContained)); } - public static boolean isCloudMediaEnabled() { - return Boolean.parseBoolean(readDeviceConfigProp(KEY_CLOUD_MEDIA_FEATURE_ENABLED)); + /** + * @return true if cloud is enabled in the device config of the given namespace, + * otherwise false. + */ + public static boolean isCloudMediaEnabled(@NonNull String namespace) { + return Boolean.parseBoolean(readDeviceConfigProp(namespace, + KEY_CLOUD_MEDIA_FEATURE_ENABLED)); } + /** + * @return the allowed providers in the device config of the given namespace. + */ @Nullable - static String getAllowedProvidersDeviceConfig() { - return readDeviceConfigProp(KEY_ALLOWED_CLOUD_PROVIDERS); + static String getAllowedProvidersDeviceConfig(@NonNull String namespace) { + return readDeviceConfigProp(namespace, KEY_ALLOWED_CLOUD_PROVIDERS); } + /** + * Enables cloud media and sets the allowed cloud provider in the namespace storage_native_boot + * and mediaprovider. + */ static void enableCloudMediaAndSetAllowedCloudProviders(@NonNull String allowedPackagesJoined) { - writeDeviceConfigProp(KEY_ALLOWED_CLOUD_PROVIDERS, allowedPackagesJoined); + enableCloudMediaAndSetAllowedCloudProviders(NAMESPACE_MEDIAPROVIDER, allowedPackagesJoined); + enableCloudMediaAndSetAllowedCloudProviders( + NAMESPACE_STORAGE_NATIVE_BOOT, allowedPackagesJoined); + } + + /** + * Enables cloud media and sets the allowed cloud provider in the given namespace. + */ + static void enableCloudMediaAndSetAllowedCloudProviders(@NonNull String namespace, + @NonNull String allowedPackagesJoined) { + writeDeviceConfigProp(namespace, KEY_ALLOWED_CLOUD_PROVIDERS, allowedPackagesJoined); assertWithMessage("Failed to update the allowed cloud providers device config") - .that(getAllowedProvidersDeviceConfig()) + .that(getAllowedProvidersDeviceConfig(namespace)) .isEqualTo(allowedPackagesJoined); - writeDeviceConfigProp(KEY_CLOUD_MEDIA_FEATURE_ENABLED, true); + writeDeviceConfigProp(namespace, KEY_CLOUD_MEDIA_FEATURE_ENABLED, true); assertWithMessage("Failed to update the cloud media feature device config") - .that(isCloudMediaEnabled()) + .that(isCloudMediaEnabled(namespace)) .isTrue(); } - - static void disableCloudMediaAndClearAllowedCloudProviders() { - writeDeviceConfigProp(KEY_CLOUD_MEDIA_FEATURE_ENABLED, false); + /** + * Disable cloud media in the given namespace. + */ + static void disableCloudMediaAndClearAllowedCloudProviders(@NonNull String namespace) { + writeDeviceConfigProp(namespace, KEY_CLOUD_MEDIA_FEATURE_ENABLED, false); assertWithMessage("Failed to update the cloud media feature device config") - .that(isCloudMediaEnabled()) + .that(isCloudMediaEnabled(namespace)) .isFalse(); - deleteDeviceConfigProp(KEY_ALLOWED_CLOUD_PROVIDERS); + deleteDeviceConfigProp(namespace, KEY_ALLOWED_CLOUD_PROVIDERS); assertWithMessage("Failed to delete the allowed cloud providers device config") - .that(getAllowedProvidersDeviceConfig()) + .that(getAllowedProvidersDeviceConfig(namespace)) .isNull(); } @@ -173,49 +201,104 @@ public class PhotoPickerCloudUtils { } @Nullable - private static String readDeviceConfigProp(@NonNull String name) { + private static String readDeviceConfigProp(@NonNull String namespace, @NonNull String name) { getUiAutomation().adoptShellPermissionIdentity(READ_DEVICE_CONFIG); try { - return DeviceConfig.getProperty(NAMESPACE_MEDIAPROVIDER, name); + return DeviceConfig.getProperty(namespace, name); } finally { getUiAutomation().dropShellPermissionIdentity(); } } - private static void writeDeviceConfigProp(@NonNull String name, boolean value) { - writeDeviceConfigProp(name, Boolean.toString(value)); + private static void writeDeviceConfigProp(@NonNull String namespace, + @NonNull String key, boolean value) { + writeDeviceConfigProp(namespace, key, Boolean.toString(value)); } - private static void writeDeviceConfigProp(@NonNull String name, @NonNull String value) { - + private static void writeDeviceConfigProp(@NonNull String namespace, @NonNull String name, + @NonNull String value) { getUiAutomation().adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG); try { - DeviceConfig.setProperty(NAMESPACE_MEDIAPROVIDER, name, value, + DeviceConfig.setProperty(namespace, name, value, /* makeDefault*/ false); } finally { getUiAutomation().dropShellPermissionIdentity(); } } - private static void deleteDeviceConfigProp(@NonNull String name) { + private static void deleteDeviceConfigProp(@NonNull String namespace, @NonNull String name) { try { getUiAutomation().adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG); if (SdkLevel.isAtLeastU()) { - DeviceConfig.deleteProperty(NAMESPACE_MEDIAPROVIDER, name); + DeviceConfig.deleteProperty(namespace, name); } else { // DeviceConfig.deleteProperty API is only available from T onwards. try { UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) .executeShellCommand( String.format("device_config delete %s %s", - NAMESPACE_MEDIAPROVIDER, name)); + namespace, name)); } catch (IOException e) { - Log.e(TAG, "Could not delete device_config " + name, e); + Log.e(TAG, String.format("Could not delete device_config %s / %s", + namespace, name), e); } } } finally { getUiAutomation().dropShellPermissionIdentity(); } } + + /** + * Set cloud provider in the device to the provided authority. + * If the provided cloud authority equals {@link PhotoPickerBaseTest#INVALID_CLOUD_PROVIDER}, + * the cloud provider will not be updated. + */ + public static void setCloudProvider(@Nullable UiDevice sDevice, + @Nullable String authority) throws IOException { + if (INVALID_CLOUD_PROVIDER.equals(authority)) { + Log.w(TAG, "Cloud provider is invalid. " + + "Ignoring the request to set the cloud provider to invalid"); + return; + } + if (authority == null) { + sDevice.executeShellCommand( + "content call" + + " --user " + UserHandle.myUserId() + + " --uri content://media/ --method set_cloud_provider --extra" + + " cloud_provider:n:null"); + } else { + sDevice.executeShellCommand( + "content call" + + " --user " + UserHandle.myUserId() + + " --uri content://media/ --method set_cloud_provider --extra" + + " cloud_provider:s:" + + authority); + } + } + + /** + * @return the current cloud provider. + */ + public static String getCurrentCloudProvider(UiDevice sDevice) throws IOException { + final String out = + sDevice.executeShellCommand( + "content call" + + " --user " + UserHandle.myUserId() + + " --uri content://media/ --method get_cloud_provider"); + return extractCloudProvider(out); + } + + private static String extractCloudProvider(String out) { + String[] splitOutput; + if (TextUtils.isEmpty(out) || ((splitOutput = out.split("=")).length < 2)) { + throw new RuntimeException("Could not get current cloud provider. Output: " + out); + } + String cloudprovider = splitOutput[1]; + cloudprovider = cloudprovider.substring(0, cloudprovider.length() - 3); + if (cloudprovider.equals("null")) { + return null; + } + return cloudprovider; + } } diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerSettingsTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerSettingsTest.java index 4fe09650771..78b3ae8c247 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerSettingsTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerSettingsTest.java @@ -16,13 +16,10 @@ package android.photopicker.cts; -import static android.photopicker.cts.PhotoPickerCloudUtils.disableCloudMediaAndClearAllowedCloudProviders; -import static android.photopicker.cts.PhotoPickerCloudUtils.enableCloudMediaAndSetAllowedCloudProviders; -import static android.photopicker.cts.PhotoPickerCloudUtils.getAllowedProvidersDeviceConfig; -import static android.photopicker.cts.PhotoPickerCloudUtils.isCloudMediaEnabled; +import static android.photopicker.cts.PhotoPickerCloudUtils.disableDeviceConfigSync; +import static android.photopicker.cts.util.PhotoPickerComponentUtils.PICKER_SETTINGS_ACTIVITY_COMPONENT; import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME; import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT; -import static android.photopicker.cts.util.PhotoPickerComponentUtils.PICKER_SETTINGS_ACTIVITY_COMPONENT; import static android.photopicker.cts.util.PhotoPickerUiUtils.isPhotoPickerVisible; import static android.photopicker.cts.util.PhotoPickerUiUtils.verifySettingsActionBarIsVisible; import static android.photopicker.cts.util.PhotoPickerUiUtils.verifySettingsActivityIsVisible; @@ -45,6 +42,7 @@ import android.provider.MediaStore; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.filters.LargeTest; import androidx.test.filters.SdkSuppress; import androidx.test.uiautomator.UiObject; @@ -52,12 +50,15 @@ import androidx.test.uiautomator.UiObjectNotFoundException; import androidx.test.uiautomator.UiSelector; import com.android.bedstead.harrier.BedsteadJUnit4; +import com.android.bedstead.harrier.DeviceState; import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile; import com.android.modules.utils.build.SdkLevel; import org.junit.After; import org.junit.Assume; import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -67,8 +68,6 @@ import org.junit.runner.RunWith; @RunWith(BedsteadJUnit4.class) public class PhotoPickerSettingsTest extends PhotoPickerBaseTest { private static final String TAG = PhotoPickerSettingsTest.class.getSimpleName(); - private static boolean sCloudMediaPreviouslyEnabled; - private static String sPreviouslyAllowedCloudProviders; private static final String EXTRA_TAB_USER_ID = "user_id"; private static final String TAB_CONTAINER_RESOURCE_ID = REGEX_PACKAGE_NAME + ":id/tab_container"; @@ -78,6 +77,11 @@ public class PhotoPickerSettingsTest extends PhotoPickerBaseTest { private static final String DEFAULT_APP_LABEL = "Photo Picker Device Tests"; private static int sPhotoPickerSettingsActivityState; private Intent mSettingsIntent; + @Nullable + private static DeviceStatePreserver sDeviceStatePreserver; + + @ClassRule @Rule + public static final DeviceState sDeviceState = new DeviceState(); @Before public void setUp() throws Exception { @@ -90,11 +94,9 @@ public class PhotoPickerSettingsTest extends PhotoPickerBaseTest { // Only enable cloud media in S+ because R cannot use Device Config APIs. if (SdkLevel.isAtLeastS()) { - // Store the current CMP configs, so that we can reset them at the end of the test. - sCloudMediaPreviouslyEnabled = isCloudMediaEnabled(); - if (sCloudMediaPreviouslyEnabled) { - sPreviouslyAllowedCloudProviders = getAllowedProvidersDeviceConfig(); - } + sDeviceStatePreserver = new DeviceStatePreserver(sDevice); + sDeviceStatePreserver.saveCurrentCloudProviderState(); + disableDeviceConfigSync(); // Enable Settings menu item in PhotoPickerActivity's overflow menu. PhotoPickerCloudUtils.enableCloudMediaAndSetAllowedCloudProviders( @@ -113,11 +115,7 @@ public class PhotoPickerSettingsTest extends PhotoPickerBaseTest { // Reset CloudMedia configs. if (SdkLevel.isAtLeastS()) { - if (sCloudMediaPreviouslyEnabled) { - enableCloudMediaAndSetAllowedCloudProviders(sPreviouslyAllowedCloudProviders); - } else { - disableCloudMediaAndClearAllowedCloudProviders(); - } + sDeviceStatePreserver.restoreCloudProviderState(); } } @@ -153,6 +151,7 @@ public class PhotoPickerSettingsTest extends PhotoPickerBaseTest { @RequireRunOnWorkProfile @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) public void testSettingsLaunchedInPersonalProfile_WorkEnabled() throws Exception { + mSettingsIntent.putExtra(EXTRA_TAB_USER_ID, sDeviceState.initialUser().id()); launchSettingsActivityWithRetry(/* retryCount */ 3, /* backoffSeedInMillis */ 500); verifySettingsActivityIsVisible(); diff --git a/tests/PhotoPicker/src/android/photopicker/cts/RemoteVideoPreviewTest.java b/tests/PhotoPicker/src/android/photopicker/cts/RemoteVideoPreviewTest.java index fa72b74ace1..67661d93171 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/RemoteVideoPreviewTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/RemoteVideoPreviewTest.java @@ -15,10 +15,9 @@ */ package android.photopicker.cts; -import static android.photopicker.cts.PhotoPickerCloudUtils.disableCloudMediaAndClearAllowedCloudProviders; + +import static android.photopicker.cts.PhotoPickerCloudUtils.disableDeviceConfigSync; import static android.photopicker.cts.PhotoPickerCloudUtils.enableCloudMediaAndSetAllowedCloudProviders; -import static android.photopicker.cts.PhotoPickerCloudUtils.getAllowedProvidersDeviceConfig; -import static android.photopicker.cts.PhotoPickerCloudUtils.isCloudMediaEnabled; import static android.photopicker.cts.PickerProviderMediaGenerator.syncCloudProvider; import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia; import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME; @@ -47,7 +46,6 @@ import android.photopicker.cts.PickerProviderMediaGenerator.MediaGenerator; import android.photopicker.cts.cloudproviders.CloudProviderPrimary; import android.photopicker.cts.cloudproviders.CloudProviderPrimary.CloudMediaSurfaceControllerImpl; import android.provider.MediaStore; -import android.util.Log; import android.util.Pair; import androidx.annotation.Nullable; @@ -93,25 +91,14 @@ public class RemoteVideoPreviewTest extends PhotoPickerBaseTest { private CloudMediaSurfaceControllerImpl mSurfaceControllerListener; // This is required to assert the order in which the APIs are called. private InOrder mAssertInOrder; - private static boolean sCloudMediaPreviouslyEnabled; - private static String sPreviouslyAllowedCloudProviders; @Nullable - private static String sPreviouslySetCloudProvider; + private static DeviceStatePreserver sDeviceStatePreserver; @BeforeClass public static void setUpBeforeClass() throws IOException { - // Store the current CMP configs, so that we can reset them at the end of the test. - sCloudMediaPreviouslyEnabled = isCloudMediaEnabled(); - if (sCloudMediaPreviouslyEnabled) { - sPreviouslyAllowedCloudProviders = getAllowedProvidersDeviceConfig(); - } - - try { - sPreviouslySetCloudProvider = getCurrentCloudProvider(); - } catch (RuntimeException e) { - Log.e(TAG, "Could not get previously set cloud provider", e); - sPreviouslySetCloudProvider = INVALID_CLOUD_PROVIDER; - } + sDeviceStatePreserver = new DeviceStatePreserver(sDevice); + sDeviceStatePreserver.saveCurrentCloudProviderState(); + disableDeviceConfigSync(); // This is a self-instrumentation test, so both "target" package name and "own" package name // should be the same (android.photopicker.cts). @@ -120,13 +107,7 @@ public class RemoteVideoPreviewTest extends PhotoPickerBaseTest { @AfterClass public static void tearDownClass() throws Exception { - // Reset CloudMedia configs. - if (sCloudMediaPreviouslyEnabled) { - enableCloudMediaAndSetAllowedCloudProviders(sPreviouslyAllowedCloudProviders); - } else { - disableCloudMediaAndClearAllowedCloudProviders(); - } - setCloudProvider(sPreviouslySetCloudProvider); + sDeviceStatePreserver.restoreCloudProviderState(); } @Before diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java index 9a39a4006cc..8c2641239e9 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java @@ -87,7 +87,7 @@ public class PhotoPickerFilesUtils { return createdImagesData; } - public static Pair<Uri, String> createImage(int userId, boolean isFavorite) throws Exception { + private static Pair<Uri, String> createImage(int userId, boolean isFavorite) throws Exception { return getPermissionAndStageMedia(R.raw.lg_g4_iso_800_jpg, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg", userId, isFavorite); } @@ -118,6 +118,15 @@ public class PhotoPickerFilesUtils { return uriList; } + /** + * Return media id from a given Uri + * @param Uri uri to parse + * @return String media id + */ + public static String getMediaId(Uri uri) throws Exception { + return uri.getLastPathSegment(); + } + public static void deleteMedia(Uri uri, Context context) throws Exception { try { ProviderTestUtils.setOwner(uri, context.getPackageName()); diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java index 13eeccca042..2bbe69ea7b4 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java @@ -35,7 +35,7 @@ import java.util.List; public class PhotoPickerUiUtils { public static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS; - public static final long TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS; + private static final long TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS; public static final String REGEX_PACKAGE_NAME = "com(.google)?.android.providers.media(.module)?"; @@ -181,27 +181,4 @@ public class PhotoPickerUiUtils { uiObject.click(); uiDevice.waitForIdle(); } - - public static String getBannerPrimaryText() throws Exception { - final UiObject bannerPrimaryText = findBannerPrimaryText(); - assertWithMessage("Timed out waiting for the banner to appear") - .that(bannerPrimaryText.waitForExists(TIMEOUT)) - .isTrue(); - return bannerPrimaryText.getText(); - } - - public static UiObject findBannerPrimaryText() { - return new UiObject(new UiSelector().resourceIdMatches( - REGEX_PACKAGE_NAME + ":id/banner_primary_text")); - } - - public static UiObject findBannerDismissButton() { - return new UiObject(new UiSelector().resourceIdMatches( - REGEX_PACKAGE_NAME + ":id/dismiss_button")); - } - - public static UiObject findBannerActionButton() { - return new UiObject(new UiSelector().resourceIdMatches( - REGEX_PACKAGE_NAME + ":id/action_button")); - } } diff --git a/tests/accessibility/AndroidTest.xml b/tests/accessibility/AndroidTest.xml index 50c779179b9..0ec90a03c74 100644 --- a/tests/accessibility/AndroidTest.xml +++ b/tests/accessibility/AndroidTest.xml @@ -24,6 +24,10 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="run-command" value="cmd accessibility set-bind-instant-service-allowed true" /> <option name="teardown-command" value="cmd accessibility set-bind-instant-service-allowed false" /> + <!-- Enable hidden APIs to allow tests to use reflection, for security tests which + check reflection abuse. This must be set before installing the test app. --> + <option name="run-command" value="settings put global hidden_api_policy 1" /> + <option name="teardown-command" value="settings delete global hidden_api_policy" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java index 6c210583e47..1c8f6847705 100644 --- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java +++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java @@ -18,12 +18,17 @@ package android.view.accessibility.cts; import static android.accessibility.cts.common.InstrumentedAccessibilityService.TIMEOUT_SERVICE_ENABLE; +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.assertSame; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; + +import android.Manifest; import android.accessibility.cts.common.AccessibilityDumpOnFailureRule; import android.accessibility.cts.common.InstrumentedAccessibilityService; import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule; @@ -32,9 +37,12 @@ import android.app.Instrumentation; import android.app.Service; import android.app.UiAutomation; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Handler; import android.platform.test.annotations.AsbSecurityTest; +import android.view.InputEvent; +import android.view.MotionEvent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; @@ -58,6 +66,9 @@ import org.junit.rules.RuleChain; import org.junit.runner.RunWith; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -202,6 +213,32 @@ public class AccessibilityManagerTest extends StsExtraBusinessLogicTestCase { assertFalse(mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener)); } + @AsbSecurityTest(cveBugId = {309426390}) + @Test + public void testInjectInputEventToInputFilter_throwsWithoutInjectEventsPermission() + throws Exception { + // Ensure the test itself doesn't have INJECT_EVENTS permission before + // calling the method that requires it and expecting failure. + assertThat(sInstrumentation.getContext().checkSelfPermission( + Manifest.permission.INJECT_EVENTS)).isEqualTo(PackageManager.PERMISSION_DENIED); + + // Use reflection to directly invoke IAccessibilityManager#injectInputEventToInputFilter. + final AccessibilityManager accessibilityManager = (AccessibilityManager) + sInstrumentation.getContext().getSystemService(Service.ACCESSIBILITY_SERVICE); + final Field serviceField = AccessibilityManager.class.getDeclaredField("mService"); + serviceField.setAccessible(true); + final Method injectInputEventToInputFilter = + Class.forName("android.view.accessibility.IAccessibilityManager") + .getDeclaredMethod("injectInputEventToInputFilter", InputEvent.class); + + final InvocationTargetException exception = assertThrows(InvocationTargetException.class, + () -> injectInputEventToInputFilter.invoke( + serviceField.get(accessibilityManager), + MotionEvent.obtain(0, 0, 0, 0, 0, 0))); + assertThat(exception).hasCauseThat().isInstanceOf(SecurityException.class); + assertThat(exception).hasCauseThat().hasMessageThat().contains("INJECT_EVENTS"); + } + @Test public void testGetInstalledAccessibilityServicesList() throws Exception { List<AccessibilityServiceInfo> installedServices = diff --git a/tests/app/WallpaperTest/src/android/app/cts/wallpapers/WallpaperManagerTest.java b/tests/app/WallpaperTest/src/android/app/cts/wallpapers/WallpaperManagerTest.java index d41303a7c99..66515d8f6b1 100644 --- a/tests/app/WallpaperTest/src/android/app/cts/wallpapers/WallpaperManagerTest.java +++ b/tests/app/WallpaperTest/src/android/app/cts/wallpapers/WallpaperManagerTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.verify; import android.app.Activity; import android.app.WallpaperColors; +import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -117,6 +118,11 @@ public class WallpaperManagerTest { private BroadcastReceiver mBroadcastReceiver; private CountDownLatch mCountDownLatch; private boolean mEnableWcg; + + // WallpaperInfo object for the built-in default wallpaper of the device. + // Always null if the device uses ImageWallpaper by default. + private WallpaperInfo mDefaultWallpaperInfo; + private static final WindowManagerStateHelper sWindowManagerStateHelper = new WindowManagerStateHelper(); @@ -157,6 +163,9 @@ public class WallpaperManagerTest { new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED)); mEnableWcg = mWallpaperManager.shouldEnableWideColorGamut(); mWallpaperManager.clear(FLAG_SYSTEM | FLAG_LOCK); + if (mDefaultWallpaperInfo == null) { + mDefaultWallpaperInfo = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM); + } assertWithMessage("Home screen wallpaper must be set after setUp()").that( mWallpaperManager.getWallpaperId(FLAG_SYSTEM)).isAtLeast(0); @@ -597,8 +606,8 @@ public class WallpaperManagerTest { @Test public void setStaticWallpaper_doesNotSetWallpaperInfo() throws IOException { - assertThat(mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM)).isNull(); - assertThat(mWallpaperManager.getWallpaperInfo(FLAG_LOCK)).isNull(); + assertNullOrDefaultWallpaper(FLAG_SYSTEM); + assertNullOrDefaultWallpaper(FLAG_LOCK); mWallpaperManager.setResource(R.drawable.icon_red, FLAG_SYSTEM); mWallpaperManager.setResource(R.drawable.icon_green, FLAG_LOCK); @@ -609,8 +618,8 @@ public class WallpaperManagerTest { @Test public void setLiveWallpaper_homeScreen_setsHomeWallpaperInfo() { - assertThat(mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM)).isNull(); - assertThat(mWallpaperManager.getWallpaperInfo(FLAG_LOCK)).isNull(); + assertNullOrDefaultWallpaper(FLAG_SYSTEM); + assertNullOrDefaultWallpaper(FLAG_LOCK); runWithShellPermissionIdentity(() -> { setWallpaperComponentAndWait(DEFAULT_COMPONENT_NAME, FLAG_SYSTEM); @@ -618,8 +627,7 @@ public class WallpaperManagerTest { assertWithMessage("Home screen").that( mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM)).isNotNull(); - assertWithMessage("Lock screen").that( - mWallpaperManager.getWallpaperInfo(FLAG_LOCK)).isNull(); + assertNullOrDefaultWallpaper(FLAG_LOCK); } @Test @@ -641,15 +649,14 @@ public class WallpaperManagerTest { @Test public void setLiveWallpaper_lockScreen_multiEngine_setsLockWallpaperInfo() { assumeTrue(mWallpaperManager.isLockscreenLiveWallpaperEnabled()); - assertThat(mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM)).isNull(); - assertThat(mWallpaperManager.getWallpaperInfo(FLAG_LOCK)).isNull(); + assertNullOrDefaultWallpaper(FLAG_SYSTEM); + assertNullOrDefaultWallpaper(FLAG_LOCK); runWithShellPermissionIdentity(() -> { setWallpaperComponentAndWait(DEFAULT_COMPONENT_NAME, FLAG_LOCK); }); - assertWithMessage("Home screen").that( - mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM)).isNull(); + assertNullOrDefaultWallpaper(FLAG_SYSTEM); assertWithMessage("Lock screen").that( mWallpaperManager.getWallpaperInfo(FLAG_LOCK)).isNotNull(); } @@ -1612,7 +1619,7 @@ public class WallpaperManagerTest { * @param which FLAG_LOCK, FLAG_SYSTEM or a combination of both. */ private void verifyColorListenerInvokedClearing(int which) { - ensureCleanState(); + ensureCleanState(which); final CountDownLatch latch = new CountDownLatch(1); @@ -1696,6 +1703,13 @@ public class WallpaperManagerTest { } } + private void assertNullOrDefaultWallpaper(int which) { + WallpaperInfo wallpaperInfo = mWallpaperManager.getWallpaperInfo(which); + if (mDefaultWallpaperInfo == null) assertThat(wallpaperInfo).isNull(); + if (wallpaperInfo == null) return; + assertThat(wallpaperInfo.getComponent()).isEqualTo(mDefaultWallpaperInfo.getComponent()); + } + private void setWallpaperComponentAndWait(ComponentName component, int which) { mWallpaperManager.setWallpaperComponentWithFlags(component, which); try { diff --git a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java index 0412f819298..dd689c48ea0 100644 --- a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java +++ b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java @@ -22,9 +22,12 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Insets; import android.test.ActivityInstrumentationTestCase2; import android.util.DisplayMetrics; +import android.util.Log; import android.view.Display; +import android.view.WindowInsets; import android.view.WindowManager; import androidx.test.uiautomator.UiDevice; @@ -247,7 +250,30 @@ public class ActivityManagerMemoryClassTest private int getScreenSize() { Context context = getInstrumentation().getTargetContext(); Configuration config = context.getResources().getConfiguration(); - return config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; + final int configScreenSize = config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; + final int minScreenSizeDp = Math.min(config.screenWidthDp, config.screenHeightDp); + // The insets size may affect screenSizeDp in different orientations. E.g., the short side + // is 720dp as the width in portrait orientation, but when the short side is the height in + // landscape orientation, the value will be smaller than 720dp because the insets of + // system bars may occupy a little space. Then the screen size from Configuration will be + // LARGE in landscape and XLARGE in portrait. So below calculation allows to return a + // smaller size definition if the size excluding insets is lower than the size threshold. + final Insets insets = getActivity().getWindowManager().getCurrentWindowMetrics() + .getWindowInsets().getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); + final int insetsSize = Math.max(insets.top + insets.bottom, insets.left + insets.right); + final int toleranceSizeDp = (int) (insetsSize / + ((float) config.densityDpi / DisplayMetrics.DENSITY_DEFAULT) + 0.5f); + Log.i("ActivityManagerMemoryClassTest", "getScreenSize: config=" + config + + " insets=" + insets + " toleranceSizeDp=" + toleranceSizeDp); + if (configScreenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE + && (minScreenSizeDp - toleranceSizeDp < 720)) { + return Configuration.SCREENLAYOUT_SIZE_LARGE; + } + if (configScreenSize == Configuration.SCREENLAYOUT_SIZE_LARGE + && (minScreenSizeDp - toleranceSizeDp < 480)) { + return Configuration.SCREENLAYOUT_SIZE_NORMAL; + } + return configScreenSize; } private void assertMemoryForScreenDensity(int memoryClass, int screenDensity, int screenSize) { diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java index 07cb853e660..9716c001570 100644 --- a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java +++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java @@ -137,19 +137,22 @@ public final class AutoFillServiceTestCase { @Override protected TestRule getMainTestRule() { - try { - // Set orientation as portrait before auto-launch an activity, - // otherwise some tests might fail due to elements not fitting - // in, IME orientation, etc... - // Many tests will hold Activity in afterActivityLaunched() by - // overriding ActivityRule. If rotating after the activity has - // started, these tests will keep the old activity. All actions - // on the wrong activity did not happen as expected. - getDropdownUiBot().setScreenOrientation(UiBot.PORTRAIT); - } catch (Exception e) { - throw new RuntimeException(e); + // Don't try to set orientation when device is in half-opened state + // The assumeFalse line in @Before would skip the half-opened tests. + if(!Helper.isDeviceInState(sContext, Helper.DeviceStateEnum.HALF_FOLDED)) { + try { + // Set orientation as portrait before auto-launch an activity, + // otherwise some tests might fail due to elements not fitting + // in, IME orientation, etc... + // Many tests will hold Activity in afterActivityLaunched() by + // overriding ActivityRule. If rotating after the activity has + // started, these tests will keep the old activity. All actions + // on the wrong activity did not happen as expected. + getDropdownUiBot().setScreenOrientation(UiBot.PORTRAIT); + } catch (Exception e) { + throw new RuntimeException(e); + } } - return getActivityRule(); } diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java index aebb835827b..15fc1ed2af4 100644 --- a/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java +++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java @@ -3072,7 +3072,7 @@ public class LoginActivityTest extends LoginActivityCommonTestCase { public void testSwitchInputMethod_noNewFillRequest() throws Exception { // TODO(b/187664861): Find better solution for small display device. mUiBot.assumeMinimumResolution(500); - + mUiBot.setScreenResolution(); // Set service enableService(); @@ -3103,5 +3103,6 @@ public class LoginActivityTest extends LoginActivityCommonTestCase { // No new fill request sReplier.assertNoUnhandledFillRequests(); + mUiBot.resetScreenResolution(); } } diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java index 95a232a0717..e10c9c22072 100644 --- a/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java +++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java @@ -246,7 +246,7 @@ public class UiBot { @Deprecated // TODO: remove once we're sure no more OEM is getting failure due to screen size public void setScreenResolution() { - if (true) { + if (false) { Log.w(TAG, "setScreenResolution(): ignored"); return; } @@ -266,7 +266,7 @@ public class UiBot { @Deprecated // TODO: remove once we're sure no more OEM is getting failure due to screen size public void resetScreenResolution() { - if (true) { + if (false) { Log.w(TAG, "resetScreenResolution(): ignored"); return; } diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java index 3ac79f928f1..a0cde43900b 100644 --- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java +++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java @@ -3583,14 +3583,12 @@ public class ExtendedCameraCharacteristicsTest extends Camera2AndroidTestCase { } // H-1-13 - int facing = staticInfo.getLensFacingChecked(); - int numOfPhysicalRgbCameras = getNumberOfRgbPhysicalCameras(facing); - boolean logicalMultiCameraReqMet = - (numOfPhysicalRgbCameras <= 1) || staticInfo.isLogicalMultiCamera(); if (isPrimaryRear) { + int facing = staticInfo.getLensFacingChecked(); + int numOfPhysicalRgbCameras = getNumberOfRgbPhysicalCameras(facing); + boolean logicalMultiCameraReqMet = + (numOfPhysicalRgbCameras <= 1) || staticInfo.isLogicalMultiCamera(); logicalMultiCameraReq.setRearLogicalMultiCameraReqMet(logicalMultiCameraReqMet); - } else { - logicalMultiCameraReq.setFrontLogicalMultiCameraReqMet(logicalMultiCameraReqMet); } // H-1-14 @@ -3627,7 +3625,6 @@ public class ExtendedCameraCharacteristicsTest extends Camera2AndroidTestCase { CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN); ultrawideZoomRatioReq.setFrontUltraWideZoomRatioReqMet(false); previewStabilizationReq.setFrontPreviewStabilizationSupported(false); - logicalMultiCameraReq.setFrontLogicalMultiCameraReqMet(false); streamUseCaseReq.setFrontStreamUseCaseSupported(false); } diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml index ee157824343..ea2ffa70a76 100644 --- a/tests/framework/base/windowmanager/AndroidManifest.xml +++ b/tests/framework/base/windowmanager/AndroidManifest.xml @@ -39,9 +39,6 @@ android:requestLegacyExternalStorage="true" android:enableOnBackInvokedCallback="false" android:testOnly="true"> - <property - android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED" - android:value="true"/> <uses-library android:name="android.test.runner"/> <uses-library android:name="androidx.window.extensions" android:required="false" /> @@ -570,10 +567,6 @@ <activity android:name="android.server.wm.SplitActivityLifecycleTest$ActivityC" android:exported="true" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/> - <activity android:name="android.server.wm.SplitActivityLifecycleTest$PortraitActivity" - android:exported="true" - android:screenOrientation="portrait" - android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/> <activity android:name="android.server.wm.SplitActivityLifecycleTest$TranslucentActivity" android:exported="true" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen" diff --git a/tests/framework/base/windowmanager/jetpack/SecondApp/AndroidManifest.xml b/tests/framework/base/windowmanager/jetpack/SecondApp/AndroidManifest.xml index 65428a30a0f..ed598187aab 100755..100644 --- a/tests/framework/base/windowmanager/jetpack/SecondApp/AndroidManifest.xml +++ b/tests/framework/base/windowmanager/jetpack/SecondApp/AndroidManifest.xml @@ -19,10 +19,18 @@ package="android.server.wm.jetpack.second"> <application> + <property + android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED" + android:value="true"/> + <activity android:name=".SecondActivity" android:exported="true" /> <activity + android:name=".PortraitActivity" + android:screenOrientation="portrait" + android:exported="true" /> + <activity android:name=".SecondActivityAllowsUntrustedEmbedding" android:allowUntrustedActivityEmbedding="true" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen" diff --git a/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/Components.java b/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/Components.java index 0d1184cae42..8d043294b49 100644 --- a/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/Components.java +++ b/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/Components.java @@ -23,6 +23,8 @@ public class Components extends ComponentsBase { public static final ComponentName SECOND_ACTIVITY = component("SecondActivity"); + public static final ComponentName PORTRAIT_ACTIVITY = component("PortraitActivity"); + public static final ComponentName SECOND_ACTIVITY_UNKNOWN_EMBEDDING_CERTS = component("SecondActivityUnknownEmbeddingCerts"); diff --git a/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/PortraitActivity.java b/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/PortraitActivity.java new file mode 100644 index 00000000000..7345fe69177 --- /dev/null +++ b/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/PortraitActivity.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.jetpack.second; + +import android.app.Activity; + +/** + * A test activity that is in portrait orientation. + */ +public class PortraitActivity extends Activity { +} diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/embedding/ActivityEmbeddingLifecycleTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/embedding/ActivityEmbeddingLifecycleTests.java index fb08897c4ca..89ca9abb350 100644 --- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/embedding/ActivityEmbeddingLifecycleTests.java +++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/embedding/ActivityEmbeddingLifecycleTests.java @@ -429,20 +429,15 @@ public class ActivityEmbeddingLifecycleTests extends ActivityEmbeddingTestBase { "secondaryActivity2", mSplitInfoConsumer); waitAndAssertResumed(secondaryActivity); waitAndAssertResumed(secondaryActivity2); - mEventLog.clear(); // Finish the middle activity secondaryActivity.finish(); waitAndAssertResumed(secondaryActivity2); waitAndAssertNotVisible(primaryActivity); - List<Pair<String, String>> expected = List.of( - transition(TestActivityWithId.class, ON_PAUSE), - transition(TestConfigChangeHandlingActivity.class, ON_STOP)); - assertTrue("Finish middle activity in multi-split", - mLifecycleTracker.waitForConditionWithTimeout(() -> - checkOrder(mEventLog, expected))); + // There is no guarantee on the order, because the removal may be delayed until the next // resumed becomes visible. + waitAndAssertActivityOnStop(TestConfigChangeHandlingActivity.class); waitAndAssertActivityOnDestroy(TestActivityWithId.class); waitAndAssertSplitStatesUpdated(); } @@ -472,20 +467,15 @@ public class ActivityEmbeddingLifecycleTests extends ActivityEmbeddingTestBase { "secondaryActivity2", mSplitInfoConsumer); waitAndAssertResumed(secondaryActivity); waitAndAssertResumed(secondaryActivity2); - mEventLog.clear(); // Finish the middle activity secondaryActivity.finish(); waitAndAssertResumed(secondaryActivity2); waitAndAssertNotVisible(primaryActivity); - List<Pair<String, String>> expected = List.of( - transition(TestActivityWithId.class, ON_PAUSE), - transition(TestConfigChangeHandlingActivity.class, ON_STOP)); - assertTrue("Finish middle activity in multi-split", - mLifecycleTracker.waitForConditionWithTimeout(() -> - checkOrder(mEventLog, expected))); + // There is no guarantee on the order, because the removal may be delayed until the next // resumed becomes visible. + waitAndAssertActivityOnStop(TestConfigChangeHandlingActivity.class); waitAndAssertActivityOnDestroy(TestActivityWithId.class); waitAndAssertSplitStatesUpdated(); } @@ -581,6 +571,10 @@ public class ActivityEmbeddingLifecycleTests extends ActivityEmbeddingTestBase { waitAndAssertNotVisible(primaryActivity); } + private void waitAndAssertActivityOnStop(Class<? extends Activity> activityClass) { + mLifecycleTracker.waitAndAssertActivityCurrentState(activityClass, ON_STOP); + } + private void waitAndAssertActivityOnDestroy(Class<? extends Activity> activityClass) { mLifecycleTracker.waitAndAssertActivityCurrentState(activityClass, ON_DESTROY); } diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/embedding/ActivityEmbeddingPolicyTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/embedding/ActivityEmbeddingPolicyTests.java index d0ad8d5255f..b4eea9d54e6 100644 --- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/embedding/ActivityEmbeddingPolicyTests.java +++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/embedding/ActivityEmbeddingPolicyTests.java @@ -18,6 +18,8 @@ package android.server.wm.jetpack.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.server.wm.jetpack.second.Components.PORTRAIT_ACTIVITY; import static android.server.wm.jetpack.second.Components.SECOND_UNTRUSTED_EMBEDDING_ACTIVITY; import static android.server.wm.jetpack.signed.Components.SIGNED_EMBEDDING_ACTIVITY; import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.createWildcardSplitPairRule; @@ -27,25 +29,33 @@ import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.EXTRA import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.startActivityFromActivity; import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.startActivityOnDisplaySingleTop; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_90; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeNotNull; +import static org.junit.Assume.assumeTrue; import android.app.Activity; import android.app.ActivityManager; import android.app.UiAutomation; import android.content.ComponentName; +import android.content.res.Configuration; import android.os.Bundle; +import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; import android.server.wm.ActivityManagerTestBase; import android.server.wm.Condition; import android.server.wm.NestedShellPermission; +import android.server.wm.RotationSession; import android.server.wm.WindowManagerState; import android.server.wm.jetpack.utils.TestActivityKnownEmbeddingCerts; import android.server.wm.jetpack.utils.TestActivityLauncher; import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity; +import android.view.Display; +import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -199,6 +209,49 @@ public class ActivityEmbeddingPolicyTests extends ActivityManagerTestBase { } } + + /** + * Verifies that the orientation request from the Activity is ignored if app uses + * ActivityEmbedding on a large screen device that supports AE. + */ + @ApiTest(apis = { + "R.attr#screenOrientation", + "android.view.WindowManager#PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED" + }) + @Test + public void testIgnoreOrientationRequestForActivityEmbeddingSplits() { + // Skip the test on devices without WM extensions. + assumeTrue(SystemProperties.getBoolean("persist.wm.extensions.enabled", false)); + + // Skip the test if this is not a large screen device + assumeTrue(getDisplayConfiguration().smallestScreenWidthDp + >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP); + + // Rotate the device to landscape + final RotationSession rotationSession = createManagedRotationSession(); + final int[] rotations = { ROTATION_0, ROTATION_90 }; + for (final int rotation : rotations) { + if (getDisplayConfiguration().orientation == ORIENTATION_LANDSCAPE) { + break; + } + rotationSession.set(rotation); + } + assumeTrue(getDisplayConfiguration().orientation == ORIENTATION_LANDSCAPE); + + // Launch a fixed-portrait activity + startActivityOnDisplay(Display.DEFAULT_DISPLAY, PORTRAIT_ACTIVITY); + + // The display should be remained in landscape. + assertEquals("The display should be remained in landscape", ORIENTATION_LANDSCAPE, + getDisplayConfiguration().orientation); + } + + private Configuration getDisplayConfiguration() { + mWmState.computeState(); + WindowManagerState.DisplayContent display = mWmState.getDisplay(DEFAULT_DISPLAY); + return display.getFullConfiguration(); + } + static void waitForOrFailWithRapidRetry(@NonNull String message, @NonNull BooleanSupplier condition) { Condition.waitFor(new Condition<>(message, condition) diff --git a/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml b/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml index 40910d7ecb4..1c38c6a2416 100644 --- a/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml +++ b/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml @@ -31,7 +31,8 @@ android:exported="true"/> <activity android:name="android.server.wm.overlay.ExitAnimationActivity" - android:exported="true"/> + android:exported="true" + android:screenOrientation="nosensor" /> <activity android:name="android.server.wm.overlay.ToastActivity" android:exported="true"/> diff --git a/tests/framework/base/windowmanager/res/drawable/background_image.xml b/tests/framework/base/windowmanager/res/drawable/background_image.xml deleted file mode 100644 index 37f40e185c0..00000000000 --- a/tests/framework/base/windowmanager/res/drawable/background_image.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="2px" - android:height="2px" - android:viewportWidth="2" - android:viewportHeight="2"> - <group> - <path - android:name="blue" - android:pathData="m0,0l1,0l0,2l-1,0l0-2z" - android:fillColor="#0000FF" /> - <path - android:name="red" - android:pathData="m1,0l1,0l0,2l-1,0l0-2z" - android:fillColor="#FF0000" /> - </group> -</vector> diff --git a/tests/framework/base/windowmanager/res/layout/background_image.xml b/tests/framework/base/windowmanager/res/layout/background_image.xml new file mode 100644 index 00000000000..2d9c798090d --- /dev/null +++ b/tests/framework/base/windowmanager/res/layout/background_image.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="#ff0000ff"/> + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="#ffff0000"/> + +</LinearLayout> diff --git a/tests/framework/base/windowmanager/res/values/styles.xml b/tests/framework/base/windowmanager/res/values/styles.xml index 00f9000bdd0..65092eca8fc 100644 --- a/tests/framework/base/windowmanager/res/values/styles.xml +++ b/tests/framework/base/windowmanager/res/values/styles.xml @@ -67,7 +67,6 @@ <item name="android:windowNoTitle">true</item> <item name="android:windowLayoutInDisplayCutoutMode">always</item> <item name="android:windowSoftInputMode">stateHidden</item> - <item name="android:windowBackground">@drawable/background_image</item> </style> <style name="BlurryDialog" parent="TranslucentDialog"> <item name="android:windowBackgroundBlurRadius">@dimen/test_background_blur_radius</item> diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java index 98f6a169930..c255eab0198 100644 --- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java +++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java @@ -202,7 +202,6 @@ public class ActivityVisibilityTests extends ActivityManagerTestBase { @Test public void testTurnScreenOnActivity() { - final LockScreenSession lockScreenSession = createManagedLockScreenSession(); final ActivitySessionClient activityClient = createManagedActivityClientSession(); testTurnScreenOnActivity(lockScreenSession, activityClient, @@ -215,7 +214,10 @@ public class ActivityVisibilityTests extends ActivityManagerTestBase { // (b/308213530). // Wait for the existing TurnScreenOnActivity to finish and the home activity to be in // stopped state as the display is OFF. - mWmState.waitForAllStoppedActivities(); + if (supportsLockScreen()) { + mWmState.waitForAllStoppedActivities(); + } + // Start TURN_SCREEN_ON_ACTIVITY launchActivity(TURN_SCREEN_ON_ACTIVITY, WINDOWING_MODE_FULLSCREEN); mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true); @@ -880,13 +882,4 @@ public class ActivityVisibilityTests extends ActivityManagerTestBase { mWmState.waitForWindowSurfaceShown(getWindowName(activityBehind), visible); mWmState.assertVisibility(activityBehind, visible); } - - /** - * Checks whether the device has automotive split-screen multitasking feature enabled - */ - private boolean hasAutomotiveSplitscreenMultitaskingFeature() { - return mContext.getPackageManager() - .hasSystemFeature(/* PackageManager.FEATURE_CAR_SPLITSCREEN_MULTITASKING */ - "android.software.car.splitscreen_multitasking") && isCar(); - } } diff --git a/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java b/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java index 71b82ae1272..5f0f9fbead9 100644 --- a/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java +++ b/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java @@ -37,7 +37,6 @@ import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.server.wm.cts.R; import android.server.wm.settings.SettingsSession; -import android.view.View; import android.view.WindowManager; import androidx.test.rule.ActivityTestRule; @@ -424,7 +423,7 @@ public class BlurTests extends WindowManagerTestBase { super.onCreate(savedInstanceState); getSplashScreen().setOnExitAnimationListener(view -> view.remove()); - setContentView(new View(this)); + setContentView(R.layout.background_image); getWindow().setDecorFitsSystemWindows(false); getWindow().getInsetsController().hide(systemBars()); diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java index 09b253c51b6..01f71b0c6ec 100644 --- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java +++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java @@ -18,6 +18,7 @@ package android.server.wm; import static android.server.wm.UiDeviceUtils.pressUnlockButton; import static android.server.wm.UiDeviceUtils.pressWakeupButton; +import static android.server.wm.WindowManagerState.STATE_RESUMED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS; @@ -35,6 +36,7 @@ import static org.junit.Assert.assertNull; import android.app.Activity; import android.app.Instrumentation; import android.app.KeyguardManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Color; @@ -42,6 +44,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; import android.os.PowerManager; +import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.service.displayhash.DisplayHashParams; import android.util.Size; @@ -78,6 +81,8 @@ import java.util.concurrent.TimeUnit; @Presubmit public class DisplayHashManagerTest { private static final int WAIT_TIME_S = 5; + private static final int TIMEOUT_MS = 1000; + private static final int SLEEP_TIMEOUT_MS = 200; private final Point mCenter = new Point(); private final Point mTestViewSize = new Point(200, 300); @@ -416,7 +421,9 @@ public class DisplayHashManagerTest { committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS); } catch (InterruptedException e) { } - waitForAllActivitiesResumed(); + ComponentName componentName = ComponentName.unflattenFromString( + "android.server.wm/android.server.wm.DisplayHashManagerTest$TestActivity"); + waitForActivityResumed(TIMEOUT_MS, componentName); byte[] expectedImageHash = new byte[]{-1, -1, 127, -1, -1, -1, 127, 127}; @@ -430,6 +437,20 @@ public class DisplayHashManagerTest { assertArrayEquals(expectedImageHash, verifiedDisplayHash.getImageHash()); } + private void waitForActivityResumed(int timeoutMs, ComponentName componentName) { + long endTime = System.currentTimeMillis() + timeoutMs; + while (endTime > System.currentTimeMillis()) { + mWmState.computeState(); + if (mWmState.hasActivityState(componentName, STATE_RESUMED)) { + SystemClock.sleep(SLEEP_TIMEOUT_MS); + mWmState.computeState(); + break; + } + SystemClock.sleep(SLEEP_TIMEOUT_MS); + mWmState.computeState(); + } + } + @Test public void testDisplayHashParams() { int width = 10; diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java index 5511c36cb06..c9e3b6ccb28 100644 --- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java +++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java @@ -742,17 +742,20 @@ public class MultiDisplayActivityLaunchTests extends MultiDisplayTestBase { .setTargetActivity(TEST_ACTIVITY).setNewTask(true) .setDisplayId(DEFAULT_DISPLAY).execute(); final int rootTaskId = mWmState.getFrontRootTaskId(DEFAULT_DISPLAY); + final int taskId = mWmState.getRootTask(rootTaskId).getTaskId(); getLaunchActivityBuilder().setUseInstrumentation() .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).setNewTask(true) .setDisplayId(DEFAULT_DISPLAY).execute(); + mayLaunchHomeActivityForCar(); + final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay(); getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true) .setTargetActivity(TEST_ACTIVITY).setNewTask(true) .setDisplayId(newDisplay.mId).execute(); assertNotEquals("Top focus root task should not be on default display", - rootTaskId, mWmState.getFocusedTaskId()); + taskId, mWmState.getRootTask(mWmState.getFocusedTaskId()).getTaskId()); mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY)); waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java index 35f565bd4e1..5dd9de79537 100644 --- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java +++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java @@ -441,6 +441,7 @@ public class MultiDisplayPolicyTests extends MultiDisplayTestBase { */ @Test public void testStackFocusSwitchOnDisplayRemoved3() { + assumeFalse(hasAutomotiveSplitscreenMultitaskingFeature()); // Start an activity on default display to determine default stack. launchActivity(BROADCAST_RECEIVER_ACTIVITY); final int focusedStackWindowingMode = mWmState.getFrontRootTaskWindowingMode( @@ -767,6 +768,7 @@ public class MultiDisplayPolicyTests extends MultiDisplayTestBase { @Test public void testAppTransitionForActivityOnDifferentDisplay() { assumeFalse(ENABLE_SHELL_TRANSITIONS); + assumeFalse(hasAutomotiveSplitscreenMultitaskingFeature()); final TestActivitySession<StandardActivity> transitionActivitySession = createManagedTestActivitySession(); // Create new simulated display. diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java index bb01bbbe86f..5bc7da3425f 100644 --- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java +++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java @@ -50,6 +50,7 @@ import static android.server.wm.app.Components.PIP_ACTIVITY_WITH_MINIMAL_SIZE; import static android.server.wm.app.Components.PIP_ACTIVITY_WITH_SAME_AFFINITY; import static android.server.wm.app.Components.PIP_ACTIVITY_WITH_TINY_MINIMAL_SIZE; import static android.server.wm.app.Components.PIP_ON_STOP_ACTIVITY; +import static android.server.wm.app.Components.PORTRAIT_ORIENTATION_ACTIVITY; import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP; import static android.server.wm.app.Components.PipActivity.ACTION_FINISH; import static android.server.wm.app.Components.PipActivity.ACTION_FINISH_LAUNCH_INTO_PIP_HOST; @@ -90,7 +91,6 @@ import static android.server.wm.app.Components.TEST_ACTIVITY; import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY; import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY; import static android.server.wm.app.Components.TestActivity.EXTRA_CONFIGURATION; -import static android.server.wm.app.Components.TestActivity.EXTRA_FIXED_ORIENTATION; import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF; import static android.server.wm.app27.Components.SDK_27_LAUNCH_ENTER_PIP_ACTIVITY; import static android.server.wm.app27.Components.SDK_27_PIP_ACTIVITY; @@ -297,13 +297,18 @@ public class PinnedStackTests extends ActivityManagerTestBase { @Test public void testEnterPipToOtherOrientation() { assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest()); + final TransitionAnimationScaleSession transitionAnimationScaleSession = + mObjectTracker.manage(new TransitionAnimationScaleSession()); + // Skip unimportant animations. + transitionAnimationScaleSession.set(0f); // Launch a portrait only app on the fullscreen stack - launchActivity(TEST_ACTIVITY, - extraString(EXTRA_FIXED_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_PORTRAIT))); + launchActivity(PORTRAIT_ORIENTATION_ACTIVITY); + mInstrumentation.getUiAutomation().syncInputTransactions(); // Launch the PiP activity fixed as landscape launchActivity(PIP_ACTIVITY, extraString(EXTRA_PIP_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_LANDSCAPE))); mWmState.waitForActivityOrientation(PIP_ACTIVITY, Configuration.ORIENTATION_LANDSCAPE); + mInstrumentation.getUiAutomation().syncInputTransactions(); // Enter PiP, and assert that the PiP is within bounds now that the device is back in // portrait mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP); @@ -1917,7 +1922,8 @@ public class PinnedStackTests extends ActivityManagerTestBase { mWmState.waitForWithAmState(wmState -> { Task task = wmState.getTaskByActivity(activityName); return task != null - && task.getActivity(activityName).getWindowingMode() == WINDOWING_MODE_PINNED; + && task.getActivity(activityName).getWindowingMode() == WINDOWING_MODE_PINNED + && task.isVisible(); }, "checking task windowing mode"); } diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java index 8cc5ca5bc01..c9178dd12e6 100644 --- a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java +++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java @@ -19,15 +19,11 @@ package android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.server.wm.SplitActivityLifecycleTest.SplitTestActivity.EXTRA_SET_RESULT_AND_FINISH; import static android.server.wm.SplitActivityLifecycleTest.SplitTestActivity.EXTRA_SHOW_WHEN_LOCKED; import static android.server.wm.WindowManagerState.STATE_STARTED; import static android.server.wm.WindowManagerState.STATE_STOPPED; import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_90; import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE; import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN; @@ -40,14 +36,11 @@ import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; -import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; import android.server.wm.WindowManagerState.TaskFragment; -import android.view.WindowManager; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.WindowContainerToken; @@ -583,44 +576,6 @@ public class SplitActivityLifecycleTest extends TaskFragmentOrganizerTestBase { "Activity A is not fully occluded and must be visible and started"); } - @Test - public void testIgnoreOrientationRequestForActivityEmbeddingSplits() { - // Skip the test on devices without WM extensions. - assumeTrue(SystemProperties.getBoolean("persist.wm.extensions.enabled", false)); - - // Skip the test if this is not a large screen device - assumeTrue(getDisplayConfiguration().smallestScreenWidthDp - >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP); - - // Rotate the device to landscape - final RotationSession rotationSession = createManagedRotationSession(); - final int[] rotations = { ROTATION_0, ROTATION_90 }; - for (final int rotation : rotations) { - if (getDisplayConfiguration().orientation == ORIENTATION_LANDSCAPE) { - break; - } - rotationSession.set(rotation); - } - assumeTrue(getDisplayConfiguration().orientation == ORIENTATION_LANDSCAPE); - - // Launch a fixed-portrait activity - Activity activity = startActivityInWindowingModeFullScreen(PortraitActivity.class); - - // The activity should be displayed in portrait while the display is remained in landscape. - assertWithMessage("The activity should be displayed in portrait") - .that(activity.getResources().getConfiguration().orientation) - .isEqualTo(ORIENTATION_PORTRAIT); - assertWithMessage("The display should be remained in landscape") - .that(getDisplayConfiguration().orientation) - .isEqualTo(ORIENTATION_LANDSCAPE); - } - - private Configuration getDisplayConfiguration() { - mWmState.computeState(); - WindowManagerState.DisplayContent display = mWmState.getDisplay(DEFAULT_DISPLAY); - return display.mFullConfiguration; - } - /** * Verifies starting an Activity on the adjacent TaskFragment and able to get the result. */ @@ -677,7 +632,6 @@ public class SplitActivityLifecycleTest extends TaskFragmentOrganizerTestBase { public static class ActivityA extends SplitTestActivity {} public static class ActivityB extends SplitTestActivity {} public static class ActivityC extends SplitTestActivity {} - public static class PortraitActivity extends SplitTestActivity {} public static class TranslucentActivity extends SplitTestActivity {} public static class SplitTestActivity extends FocusableActivity { public static final String EXTRA_SHOW_WHEN_LOCKED = "showWhenLocked"; diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java index 60a8017a9f4..ef17de28a03 100644 --- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java +++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java @@ -688,9 +688,8 @@ public class ActivityStarterTests extends ActivityLifecycleClientTestBase { .execute(); mWmState.waitForActivityState(STANDARD_ACTIVITY, STATE_RESUMED); - // Make sure the activity is finished. - assertEquals("Instance of the activity in its task must be cleared", 0, - mWmState.getActivityCountInTask(taskId, FINISH_ON_TASK_LAUNCH_ACTIVITY)); + // Make sure the activity is removed. + mWmState.waitAndAssertActivityRemoved(FINISH_ON_TASK_LAUNCH_ACTIVITY); } @Test 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 fdcd640585d..ed0c1b92393 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 @@ -961,7 +961,7 @@ public abstract class ActivityManagerTestBase { mWmState.computeState(); final List<Task> rootTasks = mWmState.getRootTasks(); for (Task rootTask : rootTasks) { - if (rootTask.getTaskId() == taskId) { + if (rootTask.getRootTaskId() == taskId) { return rootTask; } } @@ -3481,4 +3481,13 @@ public abstract class ActivityManagerTestBase { predicate).findFirst(); insetsOptional.ifPresent(insets -> insets.insetGivenFrame(inOutBounds)); } + + /** + * Checks whether the device has automotive split-screen multitasking feature enabled + */ + boolean hasAutomotiveSplitscreenMultitaskingFeature() { + return mContext.getPackageManager() + .hasSystemFeature(/* PackageManager.FEATURE_CAR_SPLITSCREEN_MULTITASKING */ + "android.software.car.splitscreen_multitasking") && isCar(); + } } 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 71d2c08ba07..b6fd395fd87 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 @@ -499,7 +499,7 @@ public class WindowManagerState { return mIsHomeRecentsComponent; } - DisplayContent getDisplay(int displayId) { + public DisplayContent getDisplay(int displayId) { for (DisplayContent display : mDisplays) { if (display.mId == displayId) { return display; @@ -1906,6 +1906,10 @@ public class WindowManagerState { mMergedOverrideConfiguration.setTo(extract(proto.mergedOverrideConfiguration)); } + public Configuration getFullConfiguration() { + return mFullConfiguration; + } + boolean isWindowingModeCompatible(int requestedWindowingMode) { if (requestedWindowingMode == WINDOWING_MODE_UNDEFINED) { return true; diff --git a/tests/inputmethod/mocklargeresourceime/AndroidManifest.xml b/tests/inputmethod/mocklargeresourceime/AndroidManifest.xml index 39e4ca01f56..d8edb1960ec 100644 --- a/tests/inputmethod/mocklargeresourceime/AndroidManifest.xml +++ b/tests/inputmethod/mocklargeresourceime/AndroidManifest.xml @@ -29,7 +29,7 @@ </intent-filter> <meta-data android:name="android.view.im" android:resource="@xml/largeresource"/> </service> - <service android:name=".services.imeservice_with_very_long_name_that_should_be_excluded_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + <service android:name=".services.imeservice_with_very_long_name_that_should_be_excluded_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" android:permission="android.permission.BIND_INPUT_METHOD" android:exported="true"> <intent-filter> <action android:name="android.view.InputMethod"/> diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java index b5092b3c652..9c94bf9f3e3 100644 --- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java +++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java @@ -425,6 +425,7 @@ public class FocusHandlingTest extends EndToEndImeTestBase { @ApiTest(apis = {"android.inputmethodservice.InputMethodService#showSoftInput"}) @Test public void testSoftInputStateAlwaysVisibleFocusEditorAfterLaunch() throws Exception { + Assume.assumeFalse(isPreventImeStartup()); final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); try (MockImeSession imeSession = createTestImeSession()) { final ImeEventStream stream = imeSession.openEventStream(); diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java index 9a03eb8ea16..90b0bb46366 100644 --- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java +++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java @@ -70,6 +70,7 @@ import com.android.cts.mockime.ImeEventStream; import com.android.cts.mockime.ImeSettings; import com.android.cts.mockime.MockImeSession; +import org.junit.Assume; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -96,6 +97,7 @@ public class ImeInsetsVisibilityTest extends EndToEndImeTestBase { @Test public void testImeVisibilityWhenImeFocusableChildPopup() throws Exception { + Assume.assumeFalse(isPreventImeStartup()); final InputMethodManager imm = getImmOrFail(); try (MockImeSession imeSession = MockImeSession.create( @@ -143,6 +145,7 @@ public class ImeInsetsVisibilityTest extends EndToEndImeTestBase { @Test public void testImeVisibilityWhenImeFocusableGravityBottomChildPopup() throws Exception { + Assume.assumeFalse(isPreventImeStartup()); final InputMethodManager imm = getImmOrFail(); try (MockImeSession imeSession = MockImeSession.create( @@ -191,6 +194,7 @@ public class ImeInsetsVisibilityTest extends EndToEndImeTestBase { @Test public void testImeVisibilityWhenImeFocusableChildPopupOverlaps() throws Exception { + Assume.assumeFalse(isPreventImeStartup()); final InputMethodManager imm = getImmOrFail(); try (MockImeSession imeSession = MockImeSession.create( @@ -240,6 +244,7 @@ public class ImeInsetsVisibilityTest extends EndToEndImeTestBase { @AppModeFull(reason = "Instant apps cannot rely on ACTION_CLOSE_SYSTEM_DIALOGS") @Test public void testEditTextPositionAndPersistWhenAboveImeWindowShown() throws Exception { + Assume.assumeFalse(isPreventImeStartup()); final InputMethodManager imm = getImmOrFail(); try (MockImeSession imeSession = MockImeSession.create( diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java index 06e7fff09ee..8d29069c057 100644 --- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java +++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java @@ -104,6 +104,7 @@ import com.android.cts.mockime.ImeEventStream; import com.android.cts.mockime.ImeSettings; import com.android.cts.mockime.MockImeSession; +import org.junit.Assume; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -210,6 +211,7 @@ public class InputMethodServiceTest extends EndToEndImeTestBase { @Test public void testSwitchInputMethod_verifiesEnabledState() throws Exception { + Assume.assumeFalse(isPreventImeStartup()); SystemUtil.runShellCommand("ime disable " + OTHER_IME_ID); try (MockImeSession imeSession = MockImeSession.create( InstrumentationRegistry.getInstrumentation().getContext(), @@ -233,6 +235,7 @@ public class InputMethodServiceTest extends EndToEndImeTestBase { } @Test public void testSwitchInputMethodWithSubtype_verifiesEnabledState() throws Exception { + Assume.assumeFalse(isPreventImeStartup()); SystemUtil.runShellCommand("ime disable " + OTHER_IME_ID); try (MockImeSession imeSession = MockImeSession.create( InstrumentationRegistry.getInstrumentation().getContext(), diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java index 266621f9855..dd0a225bbb8 100644 --- a/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java +++ b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java @@ -48,6 +48,7 @@ import com.android.cts.mockime.ImeEventStream; import com.android.cts.mockime.ImeSettings; import com.android.cts.mockime.MockImeSession; +import org.junit.Assume; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -179,6 +180,7 @@ public class SearchViewTest extends EndToEndImeTestBase { @Test public void testShowImeWhenSearchViewFocusInListView() throws Exception { + Assume.assumeFalse(isPreventImeStartup()); try (MockImeSession imeSession = MockImeSession.create( InstrumentationRegistry.getInstrumentation().getContext(), InstrumentationRegistry.getInstrumentation().getUiAutomation(), diff --git a/tests/media/common/src/android/mediav2/common/cts/EncoderConfigParams.java b/tests/media/common/src/android/mediav2/common/cts/EncoderConfigParams.java index 8afd4cbf0b7..dbd0aace848 100644 --- a/tests/media/common/src/android/mediav2/common/cts/EncoderConfigParams.java +++ b/tests/media/common/src/android/mediav2/common/cts/EncoderConfigParams.java @@ -417,6 +417,12 @@ public class EncoderConfigParams { public Builder setProfile(int profile) { this.mProfile = profile; + // encoder profile requires also level to be set prior to Android U, + // but this can be a default/unknown value. Setting this to 1 as all + // codecs use a value of 1 for lowest level. + if (mLevel < 0) { + mLevel = 1; + } return this; } diff --git a/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp b/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp index f1f051baae9..b05b275921d 100644 --- a/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp +++ b/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp @@ -320,6 +320,8 @@ bool CodecEncoderSurfaceTest::dequeueEncoderOutput(size_t bufferIndex, if (info->size > 0) { size_t buffSize; uint8_t* buf = AMediaCodec_getOutputBuffer(mEncoder, bufferIndex, &buffSize); + // NdkMediaCodec calls ABuffer::data, which already adds offset + info->offset = 0; if (mSaveToMem) { mOutputBuff->saveToMemory(buf, info); } diff --git a/tests/mediapc/common/src/android/mediapc/cts/common/PerformanceClassEvaluator.java b/tests/mediapc/common/src/android/mediapc/cts/common/PerformanceClassEvaluator.java index cd5f0f33af7..996ac26c783 100644 --- a/tests/mediapc/common/src/android/mediapc/cts/common/PerformanceClassEvaluator.java +++ b/tests/mediapc/common/src/android/mediapc/cts/common/PerformanceClassEvaluator.java @@ -1516,14 +1516,9 @@ public class PerformanceClassEvaluator { reqMet); } - public void setFrontLogicalMultiCameraReqMet(boolean reqMet) { - this.setMeasuredValue(RequirementConstants.FRONT_CAMERA_LOGICAL_MULTI_CAMERA_REQ_MET, - reqMet); - } - /** * [2.2.7.2/7.5/H-1-13] MUST support LOGICAL_MULTI_CAMERA capability for the primary - * cameras if there are greater than 1 RGB cameras facing the same direction. + * rear-facing camera if there are greater than 1 RGB rear-facing cameras. */ public static LogicalMultiCameraRequirement createLogicalMultiCameraReq() { RequiredMeasurement<Boolean> rearRequirement = RequiredMeasurement @@ -1533,16 +1528,9 @@ public class PerformanceClassEvaluator { .addRequiredValue(Build.VERSION_CODES.TIRAMISU, true) .addRequiredValue(Build.VERSION_CODES.UPSIDE_DOWN_CAKE, true) .build(); - RequiredMeasurement<Boolean> frontRequirement = RequiredMeasurement - .<Boolean>builder() - .setId(RequirementConstants.FRONT_CAMERA_LOGICAL_MULTI_CAMERA_REQ_MET) - .setPredicate(RequirementConstants.BOOLEAN_EQ) - .addRequiredValue(Build.VERSION_CODES.TIRAMISU, true) - .addRequiredValue(Build.VERSION_CODES.UPSIDE_DOWN_CAKE, true) - .build(); return new LogicalMultiCameraRequirement(RequirementConstants.R7_5__H_1_13, - rearRequirement, frontRequirement); + rearRequirement); } } diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanLargerThanVerification.java b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanLargerThanVerification.java index 35fc29b93bc..e2b7666dc2a 100644 --- a/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanLargerThanVerification.java +++ b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanLargerThanVerification.java @@ -33,7 +33,7 @@ public class MeanLargerThanVerification extends AbstractMeanVerification { public static final String PASSED_KEY = "mean_larger_than_passed"; private static final String TAG = "MeanLargerThanVerification"; - private static final float DEFAULT_GYRO_UNCAL_THRESHOLD = 0.0005f; + private static final float DEFAULT_GYRO_UNCAL_THRESHOLD = 0.0002f; private final float[] mExpected; private final float[] mThresholds; diff --git a/tests/signature/api-check/system-annotation/AndroidTest.xml b/tests/signature/api-check/system-annotation/AndroidTest.xml index f5d4db2e46a..d08de888bff 100644 --- a/tests/signature/api-check/system-annotation/AndroidTest.xml +++ b/tests/signature/api-check/system-annotation/AndroidTest.xml @@ -38,6 +38,7 @@ <option name="instrumentation-arg" key="expected-api-files" value="system-current.api.gz,system-removed.api.gz,car-system-current.api.gz,car-system-removed.api.gz" /> <option name="instrumentation-arg" key="annotation-for-exact-match" value="@android.annotation.SystemApi\(client=PRIVILEGED_APPS\)" /> <option name="runtime-hint" value="30s" /> + <option name="test-timeout" value="500000" /> <!-- Disable hidden API checks (http://b/171459260). --> <option name="hidden-api-checks" value="false" /> <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. --> diff --git a/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java b/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java index 64b664aa88a..6a3f4616434 100644 --- a/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java +++ b/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java @@ -84,6 +84,9 @@ class InterfaceChecker { HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethodSession.notifyImeHidden()"); HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethodSession.removeImeSurface()"); HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.bluetooth.BluetoothProfile.close()"); + HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract android.bluetooth.BluetoothAdapter android.bluetooth.BluetoothProfile.getAdapter()"); + HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.bluetooth.BluetoothProfile.onServiceConnected(android.os.IBinder)"); + HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.bluetooth.BluetoothProfile.onServiceDisconnected()"); } private final ResultObserver resultObserver; diff --git a/tests/surfacecontrol/src/android/view/surfacecontrol/cts/SurfaceViewSyncTest.java b/tests/surfacecontrol/src/android/view/surfacecontrol/cts/SurfaceViewSyncTest.java index ad28177b4a9..0c80ed506f6 100644 --- a/tests/surfacecontrol/src/android/view/surfacecontrol/cts/SurfaceViewSyncTest.java +++ b/tests/surfacecontrol/src/android/view/surfacecontrol/cts/SurfaceViewSyncTest.java @@ -39,6 +39,7 @@ import android.view.cts.surfacevalidator.ViewFactory; import android.widget.FrameLayout; import androidx.test.filters.LargeTest; +import androidx.test.filters.RequiresDevice; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; @@ -51,6 +52,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @LargeTest @SuppressLint("RtlHardcoded") +@RequiresDevice public class SurfaceViewSyncTest { private static final String TAG = "SurfaceViewSyncTests"; diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java index d3db6dcdf43..292091bd8c1 100644 --- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java +++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java @@ -1090,6 +1090,7 @@ public final class CarPropertyManagerTest extends AbstractCarTestCase { getInfoVinVerifier(), getInfoMakeVerifier(), getInfoModelVerifier(), + getInfoModelYearVerifier(), getInfoFuelCapacityVerifier(), getInfoFuelTypeVerifier(), getInfoEvBatteryCapacityVerifier(), @@ -1498,8 +1499,8 @@ public final class CarPropertyManagerTest extends AbstractCarTestCase { Integer.class, mCarPropertyManager) .setAllPossibleEnumValues(possibleEnumValues) .setDependentOnProperty(VehiclePropertyIds.HANDS_ON_DETECTION_ENABLED, - ImmutableSet.of(Car.PERMISSION_READ_ADAS_SETTINGS, - Car.PERMISSION_CONTROL_ADAS_SETTINGS)) + ImmutableSet.of(Car.PERMISSION_READ_DRIVER_MONITORING_SETTINGS, + Car.PERMISSION_CONTROL_DRIVER_MONITORING_SETTINGS)) .verifyErrorStates() .addReadPermission(Car.PERMISSION_READ_DRIVER_MONITORING_STATES) .build(); @@ -1529,8 +1530,8 @@ public final class CarPropertyManagerTest extends AbstractCarTestCase { Integer.class, mCarPropertyManager) .setAllPossibleEnumValues(possibleEnumValues) .setDependentOnProperty(VehiclePropertyIds.HANDS_ON_DETECTION_ENABLED, - ImmutableSet.of(Car.PERMISSION_READ_ADAS_SETTINGS, - Car.PERMISSION_CONTROL_ADAS_SETTINGS)) + ImmutableSet.of(Car.PERMISSION_READ_DRIVER_MONITORING_SETTINGS, + Car.PERMISSION_CONTROL_DRIVER_MONITORING_SETTINGS)) .verifyErrorStates() .addReadPermission(Car.PERMISSION_READ_DRIVER_MONITORING_STATES) .build(); @@ -1708,10 +1709,26 @@ public final class CarPropertyManagerTest extends AbstractCarTestCase { } @Test - public void testInfoModelYearIfSupported() { + public void testInfoModelIfSupported() { getInfoModelVerifier().verify(); } + private VehiclePropertyVerifier<Integer> getInfoModelYearVerifier() { + return VehiclePropertyVerifier.newBuilder( + VehiclePropertyIds.INFO_MODEL_YEAR, + CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ, + VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, + CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC, + Integer.class, mCarPropertyManager) + .addReadPermission(Car.PERMISSION_CAR_INFO) + .build(); + } + + @Test + public void testInfoModelYearIfSupported() { + getInfoModelYearVerifier().verify(); + } + private VehiclePropertyVerifier<Float> getInfoFuelCapacityVerifier() { return VehiclePropertyVerifier.newBuilder( VehiclePropertyIds.INFO_FUEL_CAPACITY, diff --git a/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java b/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java index 20a1c69ecd8..5bea216ef0c 100644 --- a/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java +++ b/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java @@ -1678,25 +1678,25 @@ public class VehiclePropertyVerifier<T> { verifyHvacTemperatureIsValid(suggestedTempInFahrenheit, minTempInFahrenheitTimesTen, maxTempInFahrenheitTimesTen, incrementInFahrenheitTimesTen); - int suggestedTempInCelsiusTimesTen = suggestedTempInCelsius.intValue() * 10; - int suggestedTempInFahrenheitTimesTen = suggestedTempInFahrenheit.intValue() * 10; + int suggestedTempInCelsiusTimesTen = (int) (suggestedTempInCelsius * 10f); + int suggestedTempInFahrenheitTimesTen = (int) (suggestedTempInFahrenheit * 10f); int numIncrementsCelsius = - (suggestedTempInCelsiusTimesTen - minTempInCelsiusTimesTen) - / incrementInCelsiusTimesTen; + Math.round((suggestedTempInCelsiusTimesTen - minTempInCelsiusTimesTen) + / incrementInCelsiusTimesTen.floatValue()); int numIncrementsFahrenheit = - (suggestedTempInFahrenheitTimesTen - minTempInFahrenheitTimesTen) - / incrementInFahrenheitTimesTen; + Math.round((suggestedTempInFahrenheitTimesTen - minTempInFahrenheitTimesTen) + / incrementInFahrenheitTimesTen.floatValue()); assertWithMessage( - "The temperature in Celsius must be equivalent to the temperature in" - + " Fahrenheit.") + "The temperature in celsius must map to the same temperature in fahrenheit" + + " using the HVAC_TEMPERATURE_SET config array: " + + hvacTemperatureSetConfigArray) .that(numIncrementsFahrenheit) .isEqualTo(numIncrementsCelsius); } public static void verifyHvacTemperatureIsValid(float temp, int minTempTimesTen, int maxTempTimesTen, int incrementTimesTen) { - Float tempMultiplied = temp * 10.0f; - int intTempTimesTen = tempMultiplied.intValue(); + int intTempTimesTen = (int) (temp * 10f); assertWithMessage( "The temperature value " + intTempTimesTen + " must be at least " + minTempTimesTen + " and at most " + maxTempTimesTen) diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java index d6e845d452d..ce8fdd7b094 100644 --- a/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java +++ b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java @@ -41,6 +41,7 @@ import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; +import com.android.compatibility.common.util.ApiTest; import com.android.compatibility.common.util.CddTest; import com.android.compatibility.common.util.PollingCheck; @@ -295,10 +296,11 @@ public class BugreportManagerTest extends BaseCarrierApiTest { assertExceptionThrownForMode(-1, IllegalArgumentException.class); } + @ApiTest(apis = {"android.os.BugreportParams#getMode"}) @Test public void startBugreport_invalidMode() throws Exception { - // Current max is BUGREPORT_MODE_DEFAULT (6) as defined by the AIDL. - assertExceptionThrownForMode(7, IllegalArgumentException.class); + // Current max is BUGREPORT_MODE_ONBOARDING (7) as defined by the AIDL. + assertExceptionThrownForMode(8, IllegalArgumentException.class); } /* Implementatiion of {@link BugreportCallback} that offers wrappers around execution result */ diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java index feb3a8908a0..aa8521d3176 100644 --- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java +++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java @@ -68,8 +68,8 @@ public final class DeviceConfigApiPermissionTests { .dropShellPermissionIdentity(); } /** - * Checks that when application does not have READ_DEVICE_CONFIG or WRITE_DEVICE_CONFIG - * permissions it cannot access any of DeviceConfig API methods + * Checks that when application does not have WRITE_DEVICE_CONFIG + * permissions it cannot access any write DeviceConfig API methods * @throws Exception */ @Test @@ -81,10 +81,6 @@ public final class DeviceConfigApiPermissionTests { trySetPropertiesWithoutWritePermission(violations); tryDeletePropertyWithoutWritePermission(violations); - // getters without read permission - tryGetPropertyWithoutReadPermission(violations); - tryGetPropertiesWithoutReadPermission(violations); - // Bail if we found any violations if (violations.length() > 0) { fail(violations.toString()); @@ -107,10 +103,6 @@ public final class DeviceConfigApiPermissionTests { trySetPropertyWithWritePermission(violations); trySetPropertiesWithWritePermission(violations); - // getters without read permission - tryGetPropertyWithoutReadPermission(violations); - tryGetPropertiesWithoutReadPermission(violations); - // Bail if we found any violations if (violations.length() > 0) { fail(violations.toString()); @@ -304,24 +296,6 @@ public final class DeviceConfigApiPermissionTests { } } - private void tryGetPropertyWithoutReadPermission(StringBuilder violations) { - try { - DeviceConfig.getProperty(NAMESPACE, KEY); - violations.append("DeviceConfig.getProperty() must not be accessible without " - + "READ_DEVICE_CONFIG permission.\n"); - } catch (SecurityException e) { - } - } - - private void tryGetPropertiesWithoutReadPermission(StringBuilder violations) { - try { - DeviceConfig.getProperties(NAMESPACE2); - violations.append("DeviceConfig.getProperties() must not be accessible without " - + "READ_DEVICE_CONFIG permission.\n"); - } catch (SecurityException e) { - } - } - private void trySetPropertyWithWritePermission(StringBuilder violations) { try { DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, /*makeDefault=*/ false); diff --git a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java index 5a01cbb2a53..079471633ff 100644 --- a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java +++ b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java @@ -440,7 +440,8 @@ public class FrameRateCtsActivity extends Activity { List<Float> seamedRefreshRates = new ArrayList<>(); Display.Mode[] modes = display.getSupportedModes(); for (Display.Mode otherMode : modes) { - if (!DisplayUtil.isModeSwitchSeamless(mode, otherMode)) { + if (hasSameResolution(mode, otherMode) + && !DisplayUtil.isModeSwitchSeamless(mode, otherMode)) { seamedRefreshRates.add(otherMode.getRefreshRate()); } } diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualDeviceTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualDeviceTestCase.java index fc03d5b1dd7..afa7cc1f01a 100644 --- a/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualDeviceTestCase.java +++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualDeviceTestCase.java @@ -16,6 +16,8 @@ package android.hardware.input.cts.tests; +import static android.Manifest.permission.ADD_TRUSTED_DISPLAY; +import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE; import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; import static org.junit.Assert.fail; @@ -33,6 +35,7 @@ import android.graphics.Point; import android.graphics.SurfaceTexture; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; +import android.hardware.display.VirtualDisplayConfig; import android.hardware.input.InputManager; import android.os.Bundle; import android.os.Handler; @@ -68,14 +71,11 @@ public abstract class VirtualDeviceTestCase extends InputTestCase { protected static final int DISPLAY_WIDTH = 100; protected static final int DISPLAY_HEIGHT = 100; - // Uses: - // Manifest.permission.CREATE_VIRTUAL_DEVICE, - // Manifest.permission.ADD_TRUSTED_DISPLAY - // These cannot be specified as part of the call as ADD_TRUSTED_DISPLAY is hidden and therefore - // not visible to CTS. @Rule public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( - InstrumentationRegistry.getInstrumentation().getUiAutomation()); + InstrumentationRegistry.getInstrumentation().getUiAutomation(), + CREATE_VIRTUAL_DEVICE, + ADD_TRUSTED_DISPLAY); private final CountDownLatch mLatch = new CountDownLatch(1); private final InputManager.InputDeviceListener mInputDeviceListener = @@ -109,7 +109,9 @@ public abstract class VirtualDeviceTestCase extends InputTestCase { @Override public void close() { InstrumentationRegistry.getInstrumentation().getUiAutomation() - .adoptShellPermissionIdentity(); + .adoptShellPermissionIdentity( + CREATE_VIRTUAL_DEVICE, + ADD_TRUSTED_DISPLAY); } } @@ -149,7 +151,7 @@ public abstract class VirtualDeviceTestCase extends InputTestCase { /* height= */ DISPLAY_HEIGHT, /* dpi= */ 50, /* surface= */ new Surface(new SurfaceTexture(ARBITRARY_SURFACE_TEX_ID)), - /* flags= */ 0, + /* flags= */ DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED, /* executor= */ Runnable::run, /* callback= */ null); if (mVirtualDisplay == null) { @@ -253,12 +255,11 @@ public abstract class VirtualDeviceTestCase extends InputTestCase { } public VirtualDisplay createUnownedVirtualDisplay() { - return DisplayManager.createVirtualDisplay( - "test", - /* width= */ DISPLAY_WIDTH, - /* height= */ DISPLAY_HEIGHT, - /* displayIdToMirror= */ 50, - /* surface= */ null - ); + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + DisplayManager displayManager = context.getSystemService(DisplayManager.class); + VirtualDisplayConfig config = new VirtualDisplayConfig.Builder( + "testVirtualDisplay", DISPLAY_WIDTH, DISPLAY_HEIGHT, /*densityDpi=*/100) + .build(); + return displayManager.createVirtualDisplay(config); } } diff --git a/tests/tests/hibernation/src/android/hibernation/cts/AppHibernationUtils.kt b/tests/tests/hibernation/src/android/hibernation/cts/AppHibernationUtils.kt index 17aa9be651f..a6808c0977c 100644 --- a/tests/tests/hibernation/src/android/hibernation/cts/AppHibernationUtils.kt +++ b/tests/tests/hibernation/src/android/hibernation/cts/AppHibernationUtils.kt @@ -298,7 +298,7 @@ fun openUnusedAppsNotification() { expandNotificationsWatch(UiAutomatorUtils2.getUiDevice()) waitFindObject(uiAutomation, notifSelector).click() // In wear os, notification has one additional button to open it - waitFindObject(uiAutomation, By.text("Open")).click() + waitFindObject(uiAutomation, By.textContains("Open")).click() } else { val permissionPkg: String = InstrumentationRegistry.getTargetContext() .packageManager.permissionControllerPackageName diff --git a/tests/tests/hibernation/src/android/hibernation/cts/AutoRevokeTest.kt b/tests/tests/hibernation/src/android/hibernation/cts/AutoRevokeTest.kt index 3c1269fce8d..75bb2146e2e 100644 --- a/tests/tests/hibernation/src/android/hibernation/cts/AutoRevokeTest.kt +++ b/tests/tests/hibernation/src/android/hibernation/cts/AutoRevokeTest.kt @@ -693,6 +693,12 @@ class AutoRevokeTest { } else { "Remove permissions" } + + if (hasFeatureWatch()) { + return waitFindObject( + By.checkable(true).hasDescendant(By.textStartsWith(autoRevokeText))) + } + val parent = waitFindObject( By.clickable(true) .hasDescendant(By.textStartsWith(autoRevokeText)) diff --git a/tests/tests/libcorefileio/src/android/cts/FileChannelInterProcessLockTest.java b/tests/tests/libcorefileio/src/android/cts/FileChannelInterProcessLockTest.java index 9f78d25150f..e5b25c93b9a 100644 --- a/tests/tests/libcorefileio/src/android/cts/FileChannelInterProcessLockTest.java +++ b/tests/tests/libcorefileio/src/android/cts/FileChannelInterProcessLockTest.java @@ -55,7 +55,7 @@ public class FileChannelInterProcessLockTest extends AndroidTestCase { * the service. This provides ample amount of time for the service to receive the request from * the test, then act, and respond back. */ - final static int MAX_WAIT_TIME = 7; + final static int MAX_WAIT_TIME = 20; @Override public void tearDown() throws Exception { @@ -418,27 +418,30 @@ public class FileChannelInterProcessLockTest extends AndroidTestCase { private void checkTryLockBehavior(LockType localLockType, LockType remoteLockType, ChannelType localChannelType, ChannelType remoteChannelType, boolean expectToGetLock) throws Exception { - IntentReceiver.resetReceiverState(); - - // Request that the remote lock be obtained. - getContext().startService(new Intent(getContext(), LockHoldingService.class) - .putExtra(LockHoldingService.LOCK_TYPE_KEY, remoteLockType) - .putExtra(LockHoldingService.CHANNEL_TYPE_KEY, remoteChannelType)); - - // Wait for a signal that the remote lock is definitely held. - assertTrue(IntentReceiver.lockHeldLatch.await(MAX_WAIT_TIME, SECONDS)); - - // Try to acquire the local lock in all cases and check whether it could be acquired or - // not as expected. - if (expectToGetLock) { - FileLock fileLock = acquire(getContext(), localLockType, localChannelType); - assertNotNull(fileLock); - assertTrue(fileLock.isValid()); - } else { - assertNull(acquire(getContext(), localLockType, localChannelType)); + try { + IntentReceiver.resetReceiverState(); + + // Request that the remote lock be obtained. + getContext().startService(new Intent(getContext(), LockHoldingService.class) + .putExtra(LockHoldingService.LOCK_TYPE_KEY, remoteLockType) + .putExtra(LockHoldingService.CHANNEL_TYPE_KEY, remoteChannelType)); + + // Wait for a signal that the remote lock is definitely held. + assertTrue(IntentReceiver.lockHeldLatch.await(MAX_WAIT_TIME, SECONDS)); + + // Try to acquire the local lock in all cases and check whether it could be acquired or + // not as expected. + if (expectToGetLock) { + FileLock fileLock = acquire(getContext(), localLockType, localChannelType); + assertNotNull(fileLock); + assertTrue(fileLock.isValid()); + } else { + assertNull(acquire(getContext(), localLockType, localChannelType)); + } + // Release the remote lock. + } finally { + stopService(); } - // Release the remote lock. - stopService(); } /** @@ -454,76 +457,96 @@ public class FileChannelInterProcessLockTest extends AndroidTestCase { private void checkLockBehavior(LockType localLockType, LockType remoteLockType, ChannelType localChannelType, ChannelType remoteChannelType, boolean expectToWait) throws Exception { - IntentReceiver.resetReceiverState(); - - // The amount of time the remote service should hold lock. - long remoteLockHoldTimeMillis = 5000; - - // The amount of time test should get to try to acquire the lock. - long sufficientOverlappingTimeInMillis = 2000; - - // This is the allowable delta in the time between the time recorded after the service - // released the lock and the time recorded after the test obtained the lock. - long lockReleasedAndReacquiredTimeDeltaInMillis = 500; - - // Tell the service to acquire a remote lock. - Intent sendIntent = new Intent(getContext(), LockHoldingService.class) - .putExtra(LockHoldingService.TIME_TO_HOLD_LOCK_KEY, remoteLockHoldTimeMillis) - .putExtra(LockHoldingService.LOCK_TYPE_KEY, remoteLockType) - .putExtra(LockHoldingService.CHANNEL_TYPE_KEY, remoteChannelType) - .putExtra(LockHoldingService.LOCK_BEHAVIOR_RELEASE_AND_NOTIFY_KEY, true); - - getContext().startService(sendIntent); - - // Wait for the service to hold the lock and notify for the same. - assertTrue(IntentReceiver.lockHeldLatch.await(MAX_WAIT_TIME, SECONDS)); - - long localLockNotObtainedTime = System.currentTimeMillis(); - - // Acquire the lock locally. - FileLock fileLock = acquire(getContext(), localLockType, localChannelType); - long localLockObtainedTime = System.currentTimeMillis(); - - // Wait until the remote lock has definitely been released. - assertTrue(IntentReceiver.lockReleasedLatch.await(MAX_WAIT_TIME, SECONDS)); - - Bundle remoteLockReleasedBundle = IntentReceiver.lockReleasedBundle; - long remoteLockNotReleasedTime = - remoteLockReleasedBundle.getLong(LockHoldingService.LOCK_NOT_YET_RELEASED_TIMESTAMP); - long remoteLockReleasedTime = - remoteLockReleasedBundle.getLong(LockHoldingService.LOCK_DEFINITELY_RELEASED_TIMESTAMP); - - // We want the test to be notified well before the service releases the lock, so that - // we can be sure that it tried obtaining the lock before the service actually released it. - // Therefore, a two seconds time interval provides the test to get prepare and try to obtain - // the lock. If this fails, it doesn't mean they definitely didn't overlap - // but we can't be sure and the test may not be valid. This is why we hold the lock - // remotely for a long time compared to the delays we expect for intents to propagate - // between processes. - assertTrue(remoteLockNotReleasedTime - localLockNotObtainedTime > - sufficientOverlappingTimeInMillis); - - if (expectToWait) { - - // The remoteLockReleaseTime is captured after the lock was released by the - // service. The localLockObtainedTime is captured after the lock was obtained by this - // thread. Therefore, there is a degree of slop inherent in the two times. We assert - // that they are "close" to each other, but we cannot assert any ordering. - assertTrue(Math.abs(localLockObtainedTime - remoteLockReleasedTime) < - lockReleasedAndReacquiredTimeDeltaInMillis); - } else { - // The remoteLockNotReleaseTime is captured before the lock was released by the - // service. The localLockObtainedTime is captured after the lock was obtained by this - // thread. The local thread should be able to get the lock before the remote thread - // definitely release it. If this test fails it may not indicate a problem, but it - // indicates we cannot be sure the test was successful the local lock attempt and the - // remote lock attempt did not overlap. - assertTrue(localLockObtainedTime < remoteLockNotReleasedTime); - } + try { + IntentReceiver.resetReceiverState(); - // Asserting if the fileLock is valid. - assertTrue(fileLock.isValid()); - stopService(); + // The amount of time the remote service should hold lock. + long remoteLockHoldTimeMillis = 7000; + + // The amount of time test should get to try to acquire the lock. + long sufficientOverlappingTimeInMillis = 2000; + + // This is the allowable delta in the time between the time recorded after the service + // released the lock and the time recorded after the test obtained the lock. + long lockReleasedAndReacquiredTimeDeltaInMillis = 1000; + + // Tell the service to acquire a remote lock. + Intent sendIntent = new Intent(getContext(), LockHoldingService.class) + .putExtra(LockHoldingService.TIME_TO_HOLD_LOCK_KEY, remoteLockHoldTimeMillis) + .putExtra(LockHoldingService.LOCK_TYPE_KEY, remoteLockType) + .putExtra(LockHoldingService.CHANNEL_TYPE_KEY, remoteChannelType) + .putExtra(LockHoldingService.LOCK_BEHAVIOR_RELEASE_AND_NOTIFY_KEY, true); + + getContext().startService(sendIntent); + + // Wait for the service to hold the lock and notify for the same. + assertTrue("No remote lock held notification", + IntentReceiver.lockHeldLatch.await(MAX_WAIT_TIME, SECONDS)); + + long localLockNotObtainedTime = System.currentTimeMillis(); + + // Acquire the lock locally. + FileLock fileLock = acquire(getContext(), localLockType, localChannelType); + long localLockObtainedTime = System.currentTimeMillis(); + + // Wait until the remote lock has definitely been released. + assertTrue("No remote lock release notification", + IntentReceiver.lockReleasedLatch.await(MAX_WAIT_TIME, SECONDS)); + + Bundle remoteLockReleasedBundle = IntentReceiver.lockReleasedBundle; + long remoteLockNotReleasedTime = + remoteLockReleasedBundle.getLong(LockHoldingService.LOCK_NOT_YET_RELEASED_TIMESTAMP); + long remoteLockReleasedTime = + remoteLockReleasedBundle.getLong(LockHoldingService.LOCK_DEFINITELY_RELEASED_TIMESTAMP); + + // We want the test to be notified well before the service releases the lock, so that + // we can be sure that it tried obtaining the lock before the service actually released it. + // Therefore, a two seconds time interval provides the test to get prepare and try to obtain + // the lock. If this fails, it doesn't mean they definitely didn't overlap + // but we can't be sure and the test may not be valid. This is why we hold the lock + // remotely for a long time compared to the delays we expect for intents to propagate + // between processes. + assertTrue(String.format("Remote lock release start (%d), " + + "too soon after local lock notification time (%d). " + + "Need at least %d ms", + remoteLockReleasedTime, + localLockNotObtainedTime, + sufficientOverlappingTimeInMillis + ), + remoteLockNotReleasedTime - localLockNotObtainedTime > + sufficientOverlappingTimeInMillis); + + if (expectToWait) { + + // The remoteLockReleaseTime is captured after the lock was released by the + // service. The localLockObtainedTime is captured after the lock was obtained by this + // thread. Therefore, there is a degree of slop inherent in the two times. We assert + // that they are "close" to each other, but we cannot assert any ordering. + assertTrue(String.format("Local lock obtained (%d) too long " + + "from remote lock release time (%d). " + + "Expected at most %d ms.", + localLockObtainedTime, remoteLockReleasedTime, + lockReleasedAndReacquiredTimeDeltaInMillis), + Math.abs(localLockObtainedTime - remoteLockReleasedTime) < + lockReleasedAndReacquiredTimeDeltaInMillis); + } else { + // The remoteLockNotReleaseTime is captured before the lock was released by the + // service. The localLockObtainedTime is captured after the lock was obtained by this + // thread. The local thread should be able to get the lock before the remote thread + // definitely release it. If this test fails it may not indicate a problem, but it + // indicates we cannot be sure the test was successful the local lock attempt and the + // remote lock attempt did not overlap. + assertTrue(String.format("Local lock obtained (%d) after " + + "remote lock release start (%d)", + localLockObtainedTime, remoteLockNotReleasedTime), + localLockObtainedTime < remoteLockNotReleasedTime); + } + + // Asserting if the fileLock is valid. + assertTrue(fileLock.isValid()); + } finally { + stopService(); + } } /** diff --git a/tests/tests/media/audio/res/raw/sine40dblong_44k_128kbps_LC.m4a b/tests/tests/media/audio/res/raw/sine40dblong_44k_128kbps_LC.m4a Binary files differnew file mode 100644 index 00000000000..e7aa85cc945 --- /dev/null +++ b/tests/tests/media/audio/res/raw/sine40dblong_44k_128kbps_LC.m4a diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackOffloadTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackOffloadTest.java index 9ce7f56b1fa..d56d823acaa 100644 --- a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackOffloadTest.java +++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackOffloadTest.java @@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.annotation.Nullable; import android.annotation.RawRes; +import android.content.pm.PackageManager; import android.content.res.AssetFileDescriptor; import android.media.AudioAttributes; import android.media.AudioFormat; @@ -59,6 +60,19 @@ public class AudioTrackOffloadTest extends CtsAndroidTestCase { private static final AudioAttributes DEFAULT_ATTR = new AudioAttributes.Builder().build(); + // flag to indicate if AAC related tests need to be run or not. + private boolean mTestAacSupport = false; + + @Override + protected void setUp() throws Exception { + super.setUp(); + boolean isWatch = getContext().getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_WATCH); + if (isWatch) { + mTestAacSupport = true; + } + } + public void testIsOffloadSupportedNullFormat() throws Exception { try { final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(null, @@ -125,6 +139,15 @@ public class AudioTrackOffloadTest extends CtsAndroidTestCase { getAudioFormatWithEncoding(AudioFormat.ENCODING_OPUS)); } + public void testAacLCAudioTrackOffload() throws Exception { + if (!mTestAacSupport) { + return; + } + testAudioTrackOffload(R.raw.sine40dblong_44k_128kbps_LC, + /* bitRateInkbps= */ 128, + getAudioFormatWithEncoding(AudioFormat.ENCODING_AAC_LC)); + } + @CddTest(requirement="5.5.4") public void testGaplessMP3AudioTrackOffload() throws Exception { // sine882hz3s has a gapless delay of 576 and padding of 756. diff --git a/tests/tests/media/audio/src/android/media/audio/cts/RingtoneTest.java b/tests/tests/media/audio/src/android/media/audio/cts/RingtoneTest.java index 38f7a37c6e3..c24500e2d88 100644 --- a/tests/tests/media/audio/src/android/media/audio/cts/RingtoneTest.java +++ b/tests/tests/media/audio/src/android/media/audio/cts/RingtoneTest.java @@ -16,6 +16,7 @@ package android.media.audio.cts; +import android.content.ContentProvider; import android.content.Context; import android.content.pm.PackageManager; import android.media.AudioAttributes; @@ -171,11 +172,13 @@ public class RingtoneTest extends InstrumentationTestCase { // Test an actual ringtone Uri uri = RingtoneManager.getValidRingtoneUri(mContext); assertNotNull("ringtone was unexpectedly null", uri); - RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, uri); + Uri uriWithUser = ContentProvider.maybeAddUserId(uri, mContext.getUserId()); + RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, + uriWithUser); mRingtone = RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_RINGTONE_URI); assertTrue(mRingtone.getStreamType() == AudioManager.STREAM_RING); mRingtone.play(); - assertTrue("couldn't play ringtone " + uri, mRingtone.isPlaying()); + assertTrue("couldn't play ringtone " + uriWithUser, mRingtone.isPlaying()); mRingtone.stop(); assertFalse(mRingtone.isPlaying()); } diff --git a/tests/tests/media/common/src/android/media/cts/MediaProjectionActivity.java b/tests/tests/media/common/src/android/media/cts/MediaProjectionActivity.java index 2223bc0ca54..b78e4ae7c95 100644 --- a/tests/tests/media/common/src/android/media/cts/MediaProjectionActivity.java +++ b/tests/tests/media/common/src/android/media/cts/MediaProjectionActivity.java @@ -22,10 +22,13 @@ import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; import android.os.Bundle; import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.UiObjectNotFoundException; @@ -49,6 +52,11 @@ public class MediaProjectionActivity extends Activity { private static final int PERMISSION_CODE = 1; private static final int PERMISSION_DIALOG_WAIT_MS = 1000; private static final String ACCEPT_RESOURCE_ID = "android:id/button1"; + private static final String SYSTEM_UI_PACKAGE = "com.android.systemui"; + private static final String SPINNER_RESOURCE_ID = + SYSTEM_UI_PACKAGE + ":id/screen_share_mode_spinner"; + private static final String ENTIRE_SCREEN_STRING_RES_NAME = + "screen_share_permission_dialog_option_entire_screen"; private MediaProjectionManager mProjectionManager; private MediaProjection mMediaProjection; @@ -137,34 +145,81 @@ public class MediaProjectionActivity extends Activity { /** The permission dialog will be auto-opened by the activity - find it and accept */ public void dismissPermissionDialog() { // Ensure the device is initialized before interacting with any UI elements. - final UiDevice uiDevice = UiDevice.getInstance( - InstrumentationRegistry.getInstrumentation()); - - // Scroll down the dialog; on a device with a small screen the buttons may be below the - // warning text. + UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); final boolean isWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); - if (isWatch) { - final UiScrollable scrollable = new UiScrollable(new UiSelector().scrollable(true)); - try { - if (!scrollable.scrollIntoView(new UiSelector().resourceId(ACCEPT_RESOURCE_ID))) { - Log.e(TAG, "Didn't find the accept button when scrolling"); - return; - } - Log.d(TAG, "This is a watch; we finished scrolling down to the buttons"); - } catch (UiObjectNotFoundException e) { - Log.d(TAG, "This is a watch, but there was no scrolling (the UI may not be " - + "scrollable"); + if (!isWatch) { + // if not testing on a watch device, then we need to select the entire screen option + // before pressing "Start recording" button. + if (!selectEntireScreenOption()) { + Log.e(TAG, "Couldn't select entire screen option"); } } + pressStartRecording(isWatch); + } + + private boolean selectEntireScreenOption() { + UiObject2 spinner = waitForObject(By.res(SPINNER_RESOURCE_ID)); + if (spinner == null) { + Log.e(TAG, "Couldn't find spinner to select projection mode"); + return false; + } + spinner.click(); - UiObject2 acceptButton = uiDevice.wait(Until.findObject(By.res(ACCEPT_RESOURCE_ID)), - PERMISSION_DIALOG_WAIT_MS); - if (acceptButton != null) { + UiObject2 entireScreenOption = waitForObject(By.text(getEntireScreenString())); + if (entireScreenOption == null) { + Log.e(TAG, "Couldn't find entire screen option"); + return false; + } + entireScreenOption.click(); + return true; + } + + private String getEntireScreenString() { + Resources sysUiResources; + try { + sysUiResources = getPackageManager().getResourcesForApplication(SYSTEM_UI_PACKAGE); + } catch (NameNotFoundException e) { + return null; + } + int resourceId = + sysUiResources.getIdentifier( + ENTIRE_SCREEN_STRING_RES_NAME, /* defType= */ "string", SYSTEM_UI_PACKAGE); + return sysUiResources.getString(resourceId); + } + + private void pressStartRecording(boolean isWatch) { + if (isWatch) { + scrollToStartRecordingButton(); + } + UiObject2 startRecordingButton = waitForObject(By.res(ACCEPT_RESOURCE_ID)); + if (startRecordingButton == null) { + Log.e(TAG, "Couldn't find start recording button"); + } else { Log.d(TAG, "found permission dialog after searching all windows, clicked"); - acceptButton.click(); + startRecordingButton.click(); } } + /** When testing on a small screen device, scrolls to a Start Recording button. */ + private void scrollToStartRecordingButton() { + // Scroll down the dialog; on a device with a small screen the elements may not be visible. + final UiScrollable scrollable = new UiScrollable(new UiSelector().scrollable(true)); + try { + if (!scrollable.scrollIntoView(new UiSelector().resourceId(ACCEPT_RESOURCE_ID))) { + Log.e(TAG, "Didn't find " + ACCEPT_RESOURCE_ID + " when scrolling"); + return; + } + Log.d(TAG, "This is a watch; we finished scrolling down to the ui elements"); + } catch (UiObjectNotFoundException e) { + Log.d(TAG, "This is a watch, but there was no scrolling (UI may not be scrollable"); + } + } + + private UiObject2 waitForObject(BySelector selector) { + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + return uiDevice.wait(Until.findObject(selector), PERMISSION_DIALOG_WAIT_MS); + } + @Override protected void onResume() { Log.i(TAG, "onResume"); diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeOnlyTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeOnlyTest.java index 93ab0d1c8bd..ac0c24ef0c2 100644 --- a/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeOnlyTest.java +++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeOnlyTest.java @@ -73,8 +73,9 @@ import java.util.function.Supplier; @RunWith(AndroidJUnit4.class) @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) public class DecodeOnlyTest extends MediaTestBase { - public static final boolean IS_BOARD_AT_LEAST_U = - SystemProperties.getInt("ro.board.api_level", Build.VERSION_CODES.CUR_DEVELOPMENT) + public static final boolean WAS_LAUNCHED_ON_U_OR_LATER = + SystemProperties.getInt("ro.product.first_api_level", + Build.VERSION_CODES.CUR_DEVELOPMENT) >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE; private static final String MEDIA_DIR_STRING = WorkDir.getMediaDirString(); @@ -108,7 +109,8 @@ public class DecodeOnlyTest extends MediaTestBase { @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) public void testTunneledPerfectSeekInitialPeekOnAvc() throws Exception { // Tunnel mode requires vendor support of the DECODE_ONLY feature - Assume.assumeTrue("Board API level is not Android 14 or later.", IS_BOARD_AT_LEAST_U); + Assume.assumeTrue("First API level is not Android 14 or later.", + WAS_LAUNCHED_ON_U_OR_LATER); testTunneledPerfectSeek(AVC_VIDEO, true); } @@ -116,7 +118,8 @@ public class DecodeOnlyTest extends MediaTestBase { @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) public void testTunneledPerfectSeekInitialPeekOnVp9() throws Exception { // Tunnel mode requires vendor support of the DECODE_ONLY feature - Assume.assumeTrue("Board API level is not Android 14 or later.", IS_BOARD_AT_LEAST_U); + Assume.assumeTrue("First API level is not Android 14 or later.", + WAS_LAUNCHED_ON_U_OR_LATER); testTunneledPerfectSeek(VP9_VIDEO, true); } @@ -124,7 +127,8 @@ public class DecodeOnlyTest extends MediaTestBase { @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) public void testTunneledPerfectSeekInitialPeekOnHevc() throws Exception { // Tunnel mode requires vendor support of the DECODE_ONLY feature - Assume.assumeTrue("Board API level is not Android 14 or later.", IS_BOARD_AT_LEAST_U); + Assume.assumeTrue("First API level is not Android 14 or later.", + WAS_LAUNCHED_ON_U_OR_LATER); testTunneledPerfectSeek(HEVC_VIDEO, true); } @@ -132,7 +136,8 @@ public class DecodeOnlyTest extends MediaTestBase { @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) public void testTunneledPerfectSeekInitialPeekOffAvc() throws Exception { // Tunnel mode requires vendor support of the DECODE_ONLY feature - Assume.assumeTrue("Board API level is not Android 14 or later.", IS_BOARD_AT_LEAST_U); + Assume.assumeTrue("First API level is not Android 14 or later.", + WAS_LAUNCHED_ON_U_OR_LATER); testTunneledPerfectSeek(AVC_VIDEO, false); } @@ -140,7 +145,8 @@ public class DecodeOnlyTest extends MediaTestBase { @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) public void testTunneledPerfectSeekInitialPeekOffVp9() throws Exception { // Tunnel mode requires vendor support of the DECODE_ONLY feature - Assume.assumeTrue("Board API level is not Android 14 or later.", IS_BOARD_AT_LEAST_U); + Assume.assumeTrue("First API level is not Android 14 or later.", + WAS_LAUNCHED_ON_U_OR_LATER); testTunneledPerfectSeek(VP9_VIDEO, false); } @@ -148,7 +154,8 @@ public class DecodeOnlyTest extends MediaTestBase { @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) public void testTunneledPerfectSeekInitialPeekOffHevc() throws Exception { // Tunnel mode requires vendor support of the DECODE_ONLY feature - Assume.assumeTrue("Board API level is not Android 14 or later.", IS_BOARD_AT_LEAST_U); + Assume.assumeTrue("First API level is not Android 14 or later.", + WAS_LAUNCHED_ON_U_OR_LATER); testTunneledPerfectSeek(HEVC_VIDEO, false); } @@ -159,7 +166,8 @@ public class DecodeOnlyTest extends MediaTestBase { @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) public void testTunneledTrickPlayHevc() throws Exception { // Tunnel mode requires vendor support of the DECODE_ONLY feature - Assume.assumeTrue("Board API level is not Android 14 or later.", IS_BOARD_AT_LEAST_U); + Assume.assumeTrue("First API level is not Android 14 or later.", + WAS_LAUNCHED_ON_U_OR_LATER); testTunneledTrickPlay(HEVC_VIDEO); } @@ -167,7 +175,8 @@ public class DecodeOnlyTest extends MediaTestBase { @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) public void testTunneledTrickPlayAvc() throws Exception { // Tunnel mode requires vendor support of the DECODE_ONLY feature - Assume.assumeTrue("Board API level is not Android 14 or later.", IS_BOARD_AT_LEAST_U); + Assume.assumeTrue("First API level is not Android 14 or later.", + WAS_LAUNCHED_ON_U_OR_LATER); testTunneledTrickPlay(AVC_VIDEO); } @@ -175,7 +184,8 @@ public class DecodeOnlyTest extends MediaTestBase { @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) public void testTunneledTrickPlayVp9() throws Exception { // Tunnel mode requires vendor support of the DECODE_ONLY feature - Assume.assumeTrue("Board API level is not Android 14 or later.", IS_BOARD_AT_LEAST_U); + Assume.assumeTrue("First API level is not Android 14 or later.", + WAS_LAUNCHED_ON_U_OR_LATER); testTunneledTrickPlay(VP9_VIDEO); } @@ -231,7 +241,7 @@ public class DecodeOnlyTest extends MediaTestBase { ByteBuffer inputBuffer = videoCodec.getInputBuffer(index); int sampleSize = videoExtractor.readSampleData(inputBuffer, 0); long presentationTime = videoExtractor.getSampleTime(); - int flags = videoExtractor.getSampleFlags(); + int flags = 0; if (sampleSize < 0) { flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM; sampleSize = 0; @@ -346,7 +356,7 @@ public class DecodeOnlyTest extends MediaTestBase { ByteBuffer inputBuffer = videoCodec.getInputBuffer(index); int sampleSize = videoExtractor.readSampleData(inputBuffer, 0); long presentationTime = videoExtractor.getSampleTime(); - int flags = videoExtractor.getSampleFlags(); + int flags = 0; if (sampleSize < 0) { flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM; sampleSize = 0; @@ -488,7 +498,7 @@ public class DecodeOnlyTest extends MediaTestBase { ByteBuffer inputBuffer = codec.getInputBuffer(index); int sampleSize = videoExtractor.readSampleData(inputBuffer, 0); long presentationTime = videoExtractor.getSampleTime(); - int flags = videoExtractor.getSampleFlags(); + int flags = 0; if (sampleSize < 0) { flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM; sampleSize = 0; @@ -787,7 +797,7 @@ public class DecodeOnlyTest extends MediaTestBase { ByteBuffer audioInputBuffer = mAudioCodec.getInputBuffer(index); int audioSampleSize = mAudioExtractor.readSampleData(audioInputBuffer, 0); long presentationTime = mAudioExtractor.getSampleTime(); - int flags = mAudioExtractor.getSampleFlags(); + int flags = 0; if (audioSampleSize < 0) { flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM; audioSampleSize = 0; diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java index b6d428327f8..cb88f684207 100644 --- a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java +++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java @@ -109,9 +109,10 @@ public class DecoderTest extends MediaTestBase { private static final String TAG = "DecoderTest"; private static final String REPORT_LOG_NAME = "CtsMediaDecoderTestCases"; - private static final int BOARD_SDK_LEVEL = - SystemProperties.getInt("ro.board.api_level", Build.VERSION_CODES.CUR_DEVELOPMENT); - public static final boolean IS_BOARD_AT_LEAST_S = BOARD_SDK_LEVEL >= Build.VERSION_CODES.S; + public static final boolean WAS_LAUNCHED_ON_S_OR_LATER = + SystemProperties.getInt("ro.product.first_api_level", + Build.VERSION_CODES.CUR_DEVELOPMENT) + >= Build.VERSION_CODES.S; private static boolean IS_AT_LEAST_R = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R); private static boolean IS_BEFORE_S = ApiLevelUtil.isBefore(Build.VERSION_CODES.S); @@ -3531,7 +3532,8 @@ public class DecoderTest extends MediaTestBase { @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) public void testTunneledVideoPeekOnHevc() throws Exception { // Requires vendor support of the TUNNEL_PEEK feature - Assume.assumeTrue("Board API level is not Android 12 or later.", IS_BOARD_AT_LEAST_S); + Assume.assumeTrue("First API level is not Android 12 or later.", + WAS_LAUNCHED_ON_S_OR_LATER); testTunneledVideoPeekOn(MediaFormat.MIMETYPE_VIDEO_HEVC, "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv", 25); } @@ -3544,7 +3546,8 @@ public class DecoderTest extends MediaTestBase { @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) public void testTunneledVideoPeekOnAvc() throws Exception { // Requires vendor support of the TUNNEL_PEEK feature - Assume.assumeTrue("Board API level is not Android 12 or later.", IS_BOARD_AT_LEAST_S); + Assume.assumeTrue("First API level is not Android 12 or later.", + WAS_LAUNCHED_ON_S_OR_LATER); testTunneledVideoPeekOn(MediaFormat.MIMETYPE_VIDEO_AVC, "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", 25); } @@ -3557,7 +3560,8 @@ public class DecoderTest extends MediaTestBase { @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) public void testTunneledVideoPeekOnVp9() throws Exception { // Requires vendor support of the TUNNEL_PEEK feature - Assume.assumeTrue("Board API level is not Android 12 or later.", IS_BOARD_AT_LEAST_S); + Assume.assumeTrue("First API level is not Android 12 or later.", + WAS_LAUNCHED_ON_S_OR_LATER); testTunneledVideoPeekOn(MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm", 30); @@ -3629,7 +3633,8 @@ public class DecoderTest extends MediaTestBase { @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) public void testTunneledVideoPeekOffHevc() throws Exception { // Requires vendor support of the TUNNEL_PEEK feature - Assume.assumeTrue("Board API level is not Android 12 or later.", IS_BOARD_AT_LEAST_S); + Assume.assumeTrue("First API level is not Android 12 or later.", + WAS_LAUNCHED_ON_S_OR_LATER); testTunneledVideoPeekOff(MediaFormat.MIMETYPE_VIDEO_HEVC, "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv", 25); } @@ -3642,7 +3647,8 @@ public class DecoderTest extends MediaTestBase { @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) public void testTunneledVideoPeekOffAvc() throws Exception { // Requires vendor support of the TUNNEL_PEEK feature - Assume.assumeTrue("Board API level is not Android 12 or later.", IS_BOARD_AT_LEAST_S); + Assume.assumeTrue("First API level is not Android 12 or later.", + WAS_LAUNCHED_ON_S_OR_LATER); testTunneledVideoPeekOff(MediaFormat.MIMETYPE_VIDEO_AVC, "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", 25); } @@ -3655,7 +3661,8 @@ public class DecoderTest extends MediaTestBase { @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) public void testTunneledVideoPeekOffVp9() throws Exception { // Requires vendor support of the TUNNEL_PEEK feature - Assume.assumeTrue("Board API level is not Android 12 or later.", IS_BOARD_AT_LEAST_S); + Assume.assumeTrue("First API level is not Android 12 or later.", + WAS_LAUNCHED_ON_S_OR_LATER); testTunneledVideoPeekOff(MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm", 30); diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java index ad200cd1fb9..5a9921bb18a 100644 --- a/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java +++ b/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java @@ -33,6 +33,7 @@ import android.media.Image.Plane; import android.media.ImageReader; import android.media.MediaCodec; import android.media.MediaCodecInfo; +import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.VideoCapabilities; import android.media.MediaExtractor; @@ -363,7 +364,13 @@ public class ImageReaderDecoderTest { mediaFormat = mExtractor.getTrackFormat(0); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); - + if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9) && video.contains("10bit")) { + // TODO: b/295804596 - parse color profiles in vp9 + // In some cases, webm extractor may not signal + // profile for 10-bit VP9 clips. In such cases, set profile to a + // 10-bit compatible profile. + mediaFormat.setInteger(MediaFormat.KEY_PROFILE, CodecProfileLevel.VP9Profile2); + } MediaCodecInfo info = mDecoder.getCodecInfo(); CodecCapabilities caps = info.getCapabilitiesForType(mMime); diff --git a/tests/tests/notification/src/android/app/notification/current/cts/NotificationTemplateTest.kt b/tests/tests/notification/src/android/app/notification/current/cts/NotificationTemplateTest.kt index e53e65c5347..9d61435d32d 100644 --- a/tests/tests/notification/src/android/app/notification/current/cts/NotificationTemplateTest.kt +++ b/tests/tests/notification/src/android/app/notification/current/cts/NotificationTemplateTest.kt @@ -61,7 +61,7 @@ class NotificationTemplateTest : NotificationTemplateTestBase() { } fun testWideIcon_inCollapsedState_canShowExact4By3() { - val icon = createBitmap(400, 300) + val icon = createBitmap(40, 30) val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.ic_media_play) .setContentTitle("Title") @@ -120,7 +120,7 @@ class NotificationTemplateTest : NotificationTemplateTestBase() { } fun testWideIcon_inBigBaseState_canShowExact4By3() { - val icon = createBitmap(400, 300) + val icon = createBitmap(40, 30) val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.ic_media_play) .setContentTitle("Title") @@ -174,7 +174,7 @@ class NotificationTemplateTest : NotificationTemplateTestBase() { } val picture = createBitmap(40, 30) - val icon = createBitmap(400, 300) + val icon = createBitmap(40, 30) val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.ic_media_play) .setContentTitle("Title") @@ -225,7 +225,7 @@ class NotificationTemplateTest : NotificationTemplateTestBase() { } fun testWideIcon_inBigText_canShowExact4By3() { - val icon = createBitmap(400, 300) + val icon = createBitmap(40, 30) val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.ic_media_play) .setContentTitle("Title") diff --git a/tests/tests/os/jni/android_os_cts_PerformanceHintManagerTest.cpp b/tests/tests/os/jni/android_os_cts_PerformanceHintManagerTest.cpp index 4d06b6c4016..e51a83571fa 100644 --- a/tests/tests/os/jni/android_os_cts_PerformanceHintManagerTest.cpp +++ b/tests/tests/os/jni/android_os_cts_PerformanceHintManagerTest.cpp @@ -170,8 +170,8 @@ static jstring nativeTestSetThreadsWithInvalidTid(JNIEnv* env, jobject) { std::vector<pid_t> tids; tids.push_back(2); int result = APerformanceHint_setThreads(wrapper.session(), tids.data(), 1); - if (result != EINVAL) { - return toJString(env, "setThreads did not return EINVAL"); + if (result != EINVAL && result != EPERM) { + return toJString(env, "setThreads did not return EINVAL nor EPERM"); } return nullptr; } diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt index 8a65ad3accb..e19ad7a5eda 100644 --- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt +++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt @@ -158,7 +158,6 @@ class IntentTest : PackageInstallerTestBase() { clickInstallerUIButton(INSTALL_BUTTON_ID) // Install should not have succeeded - assertEquals(RESULT_CANCELED, installation.get(TIMEOUT, TimeUnit.MILLISECONDS)) assertNotInstalled() } finally { setSecureFrp(false) diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt index 75f079e73e7..76b860e429c 100644 --- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt +++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt @@ -53,6 +53,7 @@ import java.io.File import java.util.concurrent.CompletableFuture import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit +import java.util.regex.Pattern import org.junit.After import org.junit.Assert import org.junit.Before @@ -369,7 +370,16 @@ open class PackageInstallerTestBase { * @param resId The resource ID of the button to click */ fun clickInstallerUIButton(resId: String) { - clickInstallerUIButton(By.res(PACKAGE_INSTALLER_PACKAGE_NAME, resId)) + clickInstallerUIButton(getBySelector(resId)) + } + + private fun getBySelector(id: String): BySelector { + // Normally, we wouldn't need to look for buttons from 2 different packages. + // However, to fix b/297132020, AlertController was replaced with AlertDialog and shared + // to selective partners, leading to fragmentation in which button surfaces in an OEM's + // installer app. + return By.res(Pattern.compile(String.format( + "(?:^%s|^%s):id/%s", PACKAGE_INSTALLER_PACKAGE_NAME, SYSTEM_PACKAGE_NAME, id))) } /** diff --git a/tests/tests/packageinstaller/packagescheme/src/android/packageinstaller/packagescheme/cts/PackageSchemeTestBase.kt b/tests/tests/packageinstaller/packagescheme/src/android/packageinstaller/packagescheme/cts/PackageSchemeTestBase.kt index 1f4c9bb0e6f..9686c6d5df5 100644 --- a/tests/tests/packageinstaller/packagescheme/src/android/packageinstaller/packagescheme/cts/PackageSchemeTestBase.kt +++ b/tests/tests/packageinstaller/packagescheme/src/android/packageinstaller/packagescheme/cts/PackageSchemeTestBase.kt @@ -30,6 +30,7 @@ import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until @@ -40,6 +41,7 @@ import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.function.Supplier +import java.util.regex.Pattern import org.junit.After import org.junit.Before @@ -144,17 +146,13 @@ open class PackageSchemeTestBase { mScenario!!.onActivity { val button: UiObject2? val btnName: String - // When the installed package is found, the dialog box shown is owned by - // com.android.packageinstaller (ag/19602120). On the other hand, when an error - // dialog box is shown due to app not being present or due to - // visibility constraints, the dialog box is owned by the system if (packageHasVisibility && needTargetApp) { - button = mUiDevice.wait(Until.findObject(By.res(PACKAGE_INSTALLER_PACKAGE_NAME, - NEGATIVE_BTN_ID)), DEFAULT_TIMEOUT) + button = mUiDevice.wait( + Until.findObject(getBySelector(NEGATIVE_BTN_ID)), DEFAULT_TIMEOUT) btnName = "Cancel" } else { - button = mUiDevice.wait(Until.findObject(By.res(SYSTEM_PACKAGE_NAME, - POSITIVE_BTN_ID)), DEFAULT_TIMEOUT) + button = mUiDevice.wait( + Until.findObject(getBySelector(POSITIVE_BTN_ID)), DEFAULT_TIMEOUT) btnName = "OK" } assertWithMessage("$btnName not found").that(button).isNotNull() @@ -216,4 +214,13 @@ open class PackageSchemeTestBase { return Intent(Intent.ACTION_INSTALL_PACKAGE) .setData(Uri.parse("package:$TARGET_APP_PKG_NAME")) } + + private fun getBySelector(id: String): BySelector { + // Normally, we wouldn't need to look for buttons from 2 different packages. + // However, to fix b/297132020, AlertController was replaced with AlertDialog and shared + // to selective partners, leading to fragmentation in which button surfaces in an OEM's + // installer app. + return By.res(Pattern.compile(String.format("(?:^%s|^%s):id/%s", + PACKAGE_INSTALLER_PACKAGE_NAME, SYSTEM_PACKAGE_NAME, id))) + } } diff --git a/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java b/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java index d8edc13ce3c..9b2ffce24de 100644 --- a/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java +++ b/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java @@ -35,7 +35,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.core.app.ActivityScenario; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; - +import java.util.regex.Pattern; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -47,6 +47,7 @@ import org.junit.runner.RunWith; public class TapjackingTest { private static final String LOG_TAG = TapjackingTest.class.getSimpleName(); + private static final String SYSTEM_PACKAGE_NAME = "android"; private static final String PACKAGE_INSTALLER_PACKAGE_NAME = "com.android.packageinstaller"; private static final String INSTALL_BUTTON_ID = "button1"; private static final String OVERLAY_ACTIVITY_TEXT_VIEW_ID = "overlay_description"; @@ -92,23 +93,32 @@ public class TapjackingTest { return mUiDevice.wait(Until.findObject(selector), WAIT_FOR_UI_TIMEOUT); } + private UiObject2 waitForButton(String id) { + return mUiDevice.wait(Until.findObject(getBySelector(id)), WAIT_FOR_UI_TIMEOUT); + } + + private BySelector getBySelector(String id) { + return By.res(Pattern.compile( + String.format("(?:^%s|^%s):id/%s", PACKAGE_INSTALLER_PACKAGE_NAME, SYSTEM_PACKAGE_NAME, + id))); + } + @Test public void testTapsDroppedWhenObscured() throws Exception { Log.i(LOG_TAG, "launchPackageInstaller"); launchPackageInstaller(); - UiObject2 installButton = waitForView(PACKAGE_INSTALLER_PACKAGE_NAME, INSTALL_BUTTON_ID); + UiObject2 installButton = waitForButton(INSTALL_BUTTON_ID); assertNotNull("Install button not shown", installButton); Log.i(LOG_TAG, "launchOverlayingActivity"); launchOverlayingActivity(); assertNotNull("Overlaying activity not started", waitForView(mPackageName, OVERLAY_ACTIVITY_TEXT_VIEW_ID)); - installButton = waitForView(PACKAGE_INSTALLER_PACKAGE_NAME, INSTALL_BUTTON_ID); + installButton = waitForButton(INSTALL_BUTTON_ID); assertNotNull("Cannot find install button below overlay activity", installButton); Log.i(LOG_TAG, "installButton.click"); installButton.click(); assertFalse("Tap on install button succeeded", mUiDevice.wait( - Until.gone(By.res(PACKAGE_INSTALLER_PACKAGE_NAME, INSTALL_BUTTON_ID)), - WAIT_FOR_UI_TIMEOUT)); + Until.gone(getBySelector(INSTALL_BUTTON_ID)),WAIT_FOR_UI_TIMEOUT)); mUiDevice.pressBack(); } diff --git a/tests/tests/permissionpolicy/res/raw/android_manifest.xml b/tests/tests/permissionpolicy/res/raw/android_manifest.xml index fa6033ec367..83c658bce7a 100644 --- a/tests/tests/permissionpolicy/res/raw/android_manifest.xml +++ b/tests/tests/permissionpolicy/res/raw/android_manifest.xml @@ -5960,7 +5960,7 @@ <!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register for callbacks when apps reach a certain usage time limit, etc. --> <permission android:name="android.permission.OBSERVE_APP_USAGE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @hide @TestApi @SystemApi Allows an application to change the app idle state of an app. <p>Not for use by third-party applications. --> @@ -6591,7 +6591,7 @@ it will be ignored. @hide --> <permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @SystemApi Allows entering or exiting car mode using a specified priority. This permission is required to use UiModeManager while specifying a priority for the calling diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java index 46c59e71b16..32224c4aea3 100644 --- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java +++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java @@ -96,8 +96,12 @@ public class MediaStore_Audio_PlaylistsTest { c.getString(c.getColumnIndex(Playlists.NAME))); long realDateAdded = c.getLong(c.getColumnIndex(Playlists.DATE_ADDED)); + long realDateModified = c.getLong(c.getColumnIndex(Playlists.DATE_MODIFIED)); assertTrue(realDateAdded >= dateAdded); - assertEquals(dateModified, c.getLong(c.getColumnIndex(Playlists.DATE_MODIFIED))); + // Sometimes the realDateModified is less than dateModified by exactly one second. + // We've never seen any real issues with that and now the Playlists are deprecated. + // Just changing the test to remove flakiness. + assertTrue(Math.abs(dateModified - realDateModified) <= 1); assertTrue(c.getLong(c.getColumnIndex(Playlists._ID)) > 0); c.close(); } finally { diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp index ce63982ab2c..1bca40ac1bf 100644 --- a/tests/tests/security/Android.bp +++ b/tests/tests/security/Android.bp @@ -248,3 +248,61 @@ filegroup { "test-cert-with-1-2-4-in-rotation-history", ], } + +// PackageSignatureTest is split so that it can be part of GTS suite +// See b/301094654 +android_test { + name: "GtsPackageSignatureTest", + defaults: ["cts_defaults"], + // Include both the 32 and 64 bit versions + compile_multilib: "both", + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.rules", + "androidx.test.runner", + "android-common", + "ctstestserver", + "ctstestrunner-axt", + "cts-install-lib", + "compatibility-device-util-axt", + "compatibility-common-util-devicesidelib", + "guava", + "platform-test-annotations", + "permission-test-util-lib", + "sts-device-util", + "hamcrest-library", + "NeneInternal", + ], + libs: [ + "android.test.runner", + "org.apache.http.legacy", + "android.test.base", + ], + jni_libs: [ + "libctssecurity_jni", + "libcts_jni", + "libnativehelper_compat_libc++", + "libnativehelper", + "libcutils", + "libcrypto", + "libselinux", + "libc++", + "libpcre2", + "libpackagelistparser", + "libcve_2019_2213_jni", + ], + host_required: ["CtsBackupHostTestCases"], + srcs: [ + "src/android/security/cts/PackageSignatureTest.java", + ], + platform_apis: true, + // Tag this module as a gts test artifact + test_suites: [ + "gts", + "general-tests", + ], + certificate: ":security_cts_test_certificate", + per_testcase_directory: true, + test_config: "AndroidTest_PackageSignatureTest.xml", + manifest: "AndroidManifest_PackageSignatureTest.xml", +} diff --git a/tests/tests/security/AndroidManifest_PackageSignatureTest.xml b/tests/tests/security/AndroidManifest_PackageSignatureTest.xml new file mode 100644 index 00000000000..9fd33649027 --- /dev/null +++ b/tests/tests/security/AndroidManifest_PackageSignatureTest.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.security.cts"> + + <permission-tree android:name="com.android.cts"/> + + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/> + + <!-- For FileIntegrityManager --> + <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> + + <!-- CVE-2023-21143 --> + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> + + <application android:usesCleartextTraffic="true"> + <uses-library android:name="android.test.runner"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.cts" + android:label="GTS PackageSignatureTest"> + <meta-data android:name="listener" + android:value="com.android.cts.runner.CtsTestRunListener"/> + </instrumentation> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.cts" + android:label="GTS PackageSignatureTest"> + <meta-data android:name="listener" + android:value="com.android.cts.runner.CrashParserRunListener"/> + </instrumentation> + +</manifest> diff --git a/tests/tests/security/AndroidTest_PackageSignatureTest.xml b/tests/tests/security/AndroidTest_PackageSignatureTest.xml new file mode 100644 index 00000000000..a96440d6769 --- /dev/null +++ b/tests/tests/security/AndroidTest_PackageSignatureTest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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 GtsPackageSignatureTest"> + <option name="test-suite-tag" value="gts" /> + <option name="config-descriptor:metadata" key="component" value="security" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <!-- 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" /> + <option name="config-descriptor:metadata" key="parameter" value="run_on_sdk_sandbox" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="GtsPackageSignatureTest.apk" /> + </target_preparer> + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.CrashReporter" /> + + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="teardown-command" + value="pm uninstall --user 0 android.security.cts" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="mkdir -p /data/local/tmp/cts/security" /> + <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.security.cts" /> + <!-- test-timeout unit is ms, value = 15 min --> + <option name="test-timeout" value="900000" /> + <option name="hidden-api-checks" value="false" /> + </test> + +</configuration> diff --git a/tests/tests/security/res/raw/sig_android_telephony_cts_testkey.bin b/tests/tests/security/res/raw/sig_android_telephony_cts_testkey.bin Binary files differnew file mode 100644 index 00000000000..7e2dc3ebabc --- /dev/null +++ b/tests/tests/security/res/raw/sig_android_telephony_cts_testkey.bin diff --git a/tests/tests/security/res/raw/sig_build_bazel_examples_apex_minimal.bin b/tests/tests/security/res/raw/sig_build_bazel_examples_apex_minimal.bin Binary files differnew file mode 100644 index 00000000000..5add3a77f13 --- /dev/null +++ b/tests/tests/security/res/raw/sig_build_bazel_examples_apex_minimal.bin diff --git a/tests/tests/security/res/raw/sig_cert.bin b/tests/tests/security/res/raw/sig_cert.bin Binary files differnew file mode 100644 index 00000000000..f42b8ae1e86 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cert.bin diff --git a/tests/tests/security/res/raw/sig_com_android_adbd.bin b/tests/tests/security/res/raw/sig_com_android_adbd.bin Binary files differnew file mode 100644 index 00000000000..aceb985ca47 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_adbd.bin diff --git a/tests/tests/security/res/raw/sig_com_android_adservices.bin b/tests/tests/security/res/raw/sig_com_android_adservices.bin Binary files differnew file mode 100644 index 00000000000..b5b920ba094 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_adservices.bin diff --git a/tests/tests/security/res/raw/sig_com_android_adservices_api.bin b/tests/tests/security/res/raw/sig_com_android_adservices_api.bin Binary files differnew file mode 100644 index 00000000000..d5c4a295f88 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_adservices_api.bin diff --git a/tests/tests/security/res/raw/sig_com_android_appsearch.bin b/tests/tests/security/res/raw/sig_com_android_appsearch.bin Binary files differnew file mode 100644 index 00000000000..f133a7ba4eb --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_appsearch.bin diff --git a/tests/tests/security/res/raw/sig_com_android_art.bin b/tests/tests/security/res/raw/sig_com_android_art.bin Binary files differnew file mode 100644 index 00000000000..508c56c90b8 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_art.bin diff --git a/tests/tests/security/res/raw/sig_com_android_bluetooth.bin b/tests/tests/security/res/raw/sig_com_android_bluetooth.bin Binary files differnew file mode 100644 index 00000000000..3d397c0048f --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_bluetooth.bin diff --git a/tests/tests/security/res/raw/sig_com_android_btservices.bin b/tests/tests/security/res/raw/sig_com_android_btservices.bin Binary files differnew file mode 100644 index 00000000000..9ee766c0ffb --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_btservices.bin diff --git a/tests/tests/security/res/raw/sig_com_android_car_framework.bin b/tests/tests/security/res/raw/sig_com_android_car_framework.bin Binary files differnew file mode 100644 index 00000000000..f3b7e787d4a --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_car_framework.bin diff --git a/tests/tests/security/res/raw/sig_com_android_cellbroadcast.bin b/tests/tests/security/res/raw/sig_com_android_cellbroadcast.bin Binary files differnew file mode 100644 index 00000000000..bad7b6c8786 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_cellbroadcast.bin diff --git a/tests/tests/security/res/raw/sig_com_android_compos.bin b/tests/tests/security/res/raw/sig_com_android_compos.bin Binary files differnew file mode 100644 index 00000000000..d4ba69a9ea2 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_compos.bin diff --git a/tests/tests/security/res/raw/sig_com_android_connectivity_resources.bin b/tests/tests/security/res/raw/sig_com_android_connectivity_resources.bin Binary files differnew file mode 100644 index 00000000000..e89a5520692 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_connectivity_resources.bin diff --git a/tests/tests/security/res/raw/sig_com_android_extservices.bin b/tests/tests/security/res/raw/sig_com_android_extservices.bin Binary files differnew file mode 100644 index 00000000000..790edbb4980 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_extservices.bin diff --git a/tests/tests/security/res/raw/sig_com_android_geotz.bin b/tests/tests/security/res/raw/sig_com_android_geotz.bin Binary files differnew file mode 100644 index 00000000000..aa9479aa310 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_geotz.bin diff --git a/tests/tests/security/res/raw/sig_com_android_hardware_core_permissions.bin b/tests/tests/security/res/raw/sig_com_android_hardware_core_permissions.bin Binary files differnew file mode 100644 index 00000000000..1ac04ee5275 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_hardware_core_permissions.bin diff --git a/tests/tests/security/res/raw/sig_com_android_hardware_power.bin b/tests/tests/security/res/raw/sig_com_android_hardware_power.bin Binary files differnew file mode 100644 index 00000000000..7db8788c3df --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_hardware_power.bin diff --git a/tests/tests/security/res/raw/sig_com_android_hardware_sensors.bin b/tests/tests/security/res/raw/sig_com_android_hardware_sensors.bin Binary files differnew file mode 100644 index 00000000000..c3f2e2e40ea --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_hardware_sensors.bin diff --git a/tests/tests/security/res/raw/sig_com_android_hardware_thermal.bin b/tests/tests/security/res/raw/sig_com_android_hardware_thermal.bin Binary files differnew file mode 100644 index 00000000000..03b7c4481a3 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_hardware_thermal.bin diff --git a/tests/tests/security/res/raw/sig_com_android_hardware_usb.bin b/tests/tests/security/res/raw/sig_com_android_hardware_usb.bin Binary files differnew file mode 100644 index 00000000000..7a892d82d1d --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_hardware_usb.bin diff --git a/tests/tests/security/res/raw/sig_com_android_hardware_vibrator.bin b/tests/tests/security/res/raw/sig_com_android_hardware_vibrator.bin Binary files differnew file mode 100644 index 00000000000..d33176391a9 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_hardware_vibrator.bin diff --git a/tests/tests/security/res/raw/sig_com_android_hardware_wifi.bin b/tests/tests/security/res/raw/sig_com_android_hardware_wifi.bin Binary files differnew file mode 100644 index 00000000000..9b0f190e799 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_hardware_wifi.bin diff --git a/tests/tests/security/res/raw/sig_com_android_hotspot2_osulogin.bin b/tests/tests/security/res/raw/sig_com_android_hotspot2_osulogin.bin Binary files differnew file mode 100644 index 00000000000..2bb4b5acfe1 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_hotspot2_osulogin.bin diff --git a/tests/tests/security/res/raw/sig_com_android_i18n.bin b/tests/tests/security/res/raw/sig_com_android_i18n.bin Binary files differnew file mode 100644 index 00000000000..6bcf8ecc16f --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_i18n.bin diff --git a/tests/tests/security/res/raw/sig_com_android_ipsec.bin b/tests/tests/security/res/raw/sig_com_android_ipsec.bin Binary files differnew file mode 100644 index 00000000000..319484ac734 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_ipsec.bin diff --git a/tests/tests/security/res/raw/sig_com_android_mediaprovider.bin b/tests/tests/security/res/raw/sig_com_android_mediaprovider.bin Binary files differnew file mode 100644 index 00000000000..c87618f32ee --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_mediaprovider.bin diff --git a/tests/tests/security/res/raw/sig_com_android_neuralnetworks.bin b/tests/tests/security/res/raw/sig_com_android_neuralnetworks.bin Binary files differnew file mode 100644 index 00000000000..05cb7b9fd7a --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_neuralnetworks.bin diff --git a/tests/tests/security/res/raw/sig_com_android_os_statsd.bin b/tests/tests/security/res/raw/sig_com_android_os_statsd.bin Binary files differnew file mode 100644 index 00000000000..ed8693be0f9 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_os_statsd.bin diff --git a/tests/tests/security/res/raw/sig_com_android_permission.bin b/tests/tests/security/res/raw/sig_com_android_permission.bin Binary files differnew file mode 100644 index 00000000000..e9e5a692dff --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_permission.bin diff --git a/tests/tests/security/res/raw/sig_com_android_rkpd.bin b/tests/tests/security/res/raw/sig_com_android_rkpd.bin Binary files differnew file mode 100644 index 00000000000..c49bfd057fa --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_rkpd.bin diff --git a/tests/tests/security/res/raw/sig_com_android_runtime.bin b/tests/tests/security/res/raw/sig_com_android_runtime.bin Binary files differnew file mode 100644 index 00000000000..e59f0f37eda --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_runtime.bin diff --git a/tests/tests/security/res/raw/sig_com_android_safetycenter_resources.bin b/tests/tests/security/res/raw/sig_com_android_safetycenter_resources.bin Binary files differnew file mode 100644 index 00000000000..d9f97a4f693 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_safetycenter_resources.bin diff --git a/tests/tests/security/res/raw/sig_com_android_sdkext.bin b/tests/tests/security/res/raw/sig_com_android_sdkext.bin Binary files differnew file mode 100644 index 00000000000..26a9b920fcf --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_sdkext.bin diff --git a/tests/tests/security/res/raw/sig_com_android_uwb.bin b/tests/tests/security/res/raw/sig_com_android_uwb.bin Binary files differnew file mode 100644 index 00000000000..1118522aa4e --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_uwb.bin diff --git a/tests/tests/security/res/raw/sig_com_android_uwb_resources.bin b/tests/tests/security/res/raw/sig_com_android_uwb_resources.bin Binary files differnew file mode 100644 index 00000000000..55f326cb5f3 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_uwb_resources.bin diff --git a/tests/tests/security/res/raw/sig_com_android_vibrator_drv2624.bin b/tests/tests/security/res/raw/sig_com_android_vibrator_drv2624.bin Binary files differnew file mode 100644 index 00000000000..cd7e33cbfba --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_vibrator_drv2624.bin diff --git a/tests/tests/security/res/raw/sig_com_android_vibrator_sunfish.bin b/tests/tests/security/res/raw/sig_com_android_vibrator_sunfish.bin Binary files differnew file mode 100644 index 00000000000..42e01d60c7d --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_vibrator_sunfish.bin diff --git a/tests/tests/security/res/raw/sig_com_android_virt.bin b/tests/tests/security/res/raw/sig_com_android_virt.bin Binary files differnew file mode 100644 index 00000000000..89c91e356c1 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_virt.bin diff --git a/tests/tests/security/res/raw/sig_com_android_vndk.bin b/tests/tests/security/res/raw/sig_com_android_vndk.bin Binary files differnew file mode 100644 index 00000000000..e6b006bf466 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_vndk.bin diff --git a/tests/tests/security/res/raw/sig_com_android_wifi.bin b/tests/tests/security/res/raw/sig_com_android_wifi.bin Binary files differnew file mode 100644 index 00000000000..469ee37f822 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_wifi.bin diff --git a/tests/tests/security/res/raw/sig_com_android_wifi_dialog.bin b/tests/tests/security/res/raw/sig_com_android_wifi_dialog.bin Binary files differnew file mode 100644 index 00000000000..1fc37ebb576 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_wifi_dialog.bin diff --git a/tests/tests/security/res/raw/sig_com_android_wifi_resources.bin b/tests/tests/security/res/raw/sig_com_android_wifi_resources.bin Binary files differnew file mode 100644 index 00000000000..09016e017b9 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_android_wifi_resources.bin diff --git a/tests/tests/security/res/raw/sig_com_google_cf_apex.bin b/tests/tests/security/res/raw/sig_com_google_cf_apex.bin Binary files differnew file mode 100644 index 00000000000..f894bf6b821 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_google_cf_apex.bin diff --git a/tests/tests/security/res/raw/sig_com_google_emulated_camera_provider_hal.bin b/tests/tests/security/res/raw/sig_com_google_emulated_camera_provider_hal.bin Binary files differnew file mode 100644 index 00000000000..e011b6206b7 --- /dev/null +++ b/tests/tests/security/res/raw/sig_com_google_emulated_camera_provider_hal.bin diff --git a/tests/tests/security/res/raw/sig_cts_appsearch_helper_cert_a.bin b/tests/tests/security/res/raw/sig_cts_appsearch_helper_cert_a.bin Binary files differnew file mode 100644 index 00000000000..af5d3136be3 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_appsearch_helper_cert_a.bin diff --git a/tests/tests/security/res/raw/sig_cts_appsearch_helper_cert_b.bin b/tests/tests/security/res/raw/sig_cts_appsearch_helper_cert_b.bin Binary files differnew file mode 100644 index 00000000000..d8fcc87b5cf --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_appsearch_helper_cert_b.bin diff --git a/tests/tests/security/res/raw/sig_cts_appsearch_hosttest_helper_cert_a.bin b/tests/tests/security/res/raw/sig_cts_appsearch_hosttest_helper_cert_a.bin Binary files differnew file mode 100644 index 00000000000..c8c1806eb32 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_appsearch_hosttest_helper_cert_a.bin diff --git a/tests/tests/security/res/raw/sig_cts_appsearch_hosttest_helper_cert_b.bin b/tests/tests/security/res/raw/sig_cts_appsearch_hosttest_helper_cert_b.bin Binary files differnew file mode 100644 index 00000000000..802cbc0e2f9 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_appsearch_hosttest_helper_cert_b.bin diff --git a/tests/tests/security/res/raw/sig_cts_blob_helper_cert.bin b/tests/tests/security/res/raw/sig_cts_blob_helper_cert.bin Binary files differnew file mode 100644 index 00000000000..6e4c31112ae --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_blob_helper_cert.bin diff --git a/tests/tests/security/res/raw/sig_cts_blob_helper_cert2.bin b/tests/tests/security/res/raw/sig_cts_blob_helper_cert2.bin Binary files differnew file mode 100644 index 00000000000..35347a65cc0 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_blob_helper_cert2.bin diff --git a/tests/tests/security/res/raw/sig_cts_keyset_test_a.bin b/tests/tests/security/res/raw/sig_cts_keyset_test_a.bin Binary files differnew file mode 100644 index 00000000000..4d22159f17d --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_keyset_test_a.bin diff --git a/tests/tests/security/res/raw/sig_cts_keyset_test_b.bin b/tests/tests/security/res/raw/sig_cts_keyset_test_b.bin Binary files differnew file mode 100644 index 00000000000..dbeeefd1907 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_keyset_test_b.bin diff --git a/tests/tests/security/res/raw/sig_cts_keyset_test_c.bin b/tests/tests/security/res/raw/sig_cts_keyset_test_c.bin Binary files differnew file mode 100644 index 00000000000..265ac9a8058 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_keyset_test_c.bin diff --git a/tests/tests/security/res/raw/sig_cts_keyset_test_ec_a.bin b/tests/tests/security/res/raw/sig_cts_keyset_test_ec_a.bin Binary files differnew file mode 100644 index 00000000000..356a5ab5116 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_keyset_test_ec_a.bin diff --git a/tests/tests/security/res/raw/sig_cts_net_app.bin b/tests/tests/security/res/raw/sig_cts_net_app.bin Binary files differnew file mode 100644 index 00000000000..ff683eda7f2 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_net_app.bin diff --git a/tests/tests/security/res/raw/sig_cts_testkey1.bin b/tests/tests/security/res/raw/sig_cts_testkey1.bin Binary files differnew file mode 100644 index 00000000000..ca90fe2d9a5 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_testkey1.bin diff --git a/tests/tests/security/res/raw/sig_cts_testkey2.bin b/tests/tests/security/res/raw/sig_cts_testkey2.bin Binary files differnew file mode 100644 index 00000000000..4371c215e51 --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_testkey2.bin diff --git a/tests/tests/security/res/raw/sig_cts_uicc_2021.bin b/tests/tests/security/res/raw/sig_cts_uicc_2021.bin Binary files differnew file mode 100644 index 00000000000..7125291a08a --- /dev/null +++ b/tests/tests/security/res/raw/sig_cts_uicc_2021.bin diff --git a/tests/tests/security/res/raw/sig_ec_p256.bin b/tests/tests/security/res/raw/sig_ec_p256.bin Binary files differnew file mode 100644 index 00000000000..e611e3d9383 --- /dev/null +++ b/tests/tests/security/res/raw/sig_ec_p256.bin diff --git a/tests/tests/security/res/raw/sig_ec_p256_2.bin b/tests/tests/security/res/raw/sig_ec_p256_2.bin Binary files differnew file mode 100644 index 00000000000..7723beab571 --- /dev/null +++ b/tests/tests/security/res/raw/sig_ec_p256_2.bin diff --git a/tests/tests/security/res/raw/sig_ec_p256_3.bin b/tests/tests/security/res/raw/sig_ec_p256_3.bin Binary files differnew file mode 100644 index 00000000000..cc82af9c031 --- /dev/null +++ b/tests/tests/security/res/raw/sig_ec_p256_3.bin diff --git a/tests/tests/security/res/raw/sig_ec_p256_4.bin b/tests/tests/security/res/raw/sig_ec_p256_4.bin Binary files differnew file mode 100644 index 00000000000..29980db219a --- /dev/null +++ b/tests/tests/security/res/raw/sig_ec_p256_4.bin diff --git a/tests/tests/security/res/raw/sig_ec_p256_5.bin b/tests/tests/security/res/raw/sig_ec_p256_5.bin Binary files differnew file mode 100644 index 00000000000..e62d5bb5673 --- /dev/null +++ b/tests/tests/security/res/raw/sig_ec_p256_5.bin diff --git a/tests/tests/security/res/raw/sig_keyset_A.bin b/tests/tests/security/res/raw/sig_keyset_A.bin Binary files differnew file mode 100644 index 00000000000..e484ec313a4 --- /dev/null +++ b/tests/tests/security/res/raw/sig_keyset_A.bin diff --git a/tests/tests/security/res/raw/sig_keyset_B.bin b/tests/tests/security/res/raw/sig_keyset_B.bin Binary files differnew file mode 100644 index 00000000000..b95719ce728 --- /dev/null +++ b/tests/tests/security/res/raw/sig_keyset_B.bin diff --git a/tests/tests/security/res/raw/sig_rro_remounted_test_a.bin b/tests/tests/security/res/raw/sig_rro_remounted_test_a.bin Binary files differnew file mode 100644 index 00000000000..24d2d5583ea --- /dev/null +++ b/tests/tests/security/res/raw/sig_rro_remounted_test_a.bin diff --git a/tests/tests/security/res/raw/sig_rsa_2048.bin b/tests/tests/security/res/raw/sig_rsa_2048.bin Binary files differnew file mode 100644 index 00000000000..d61baefcd67 --- /dev/null +++ b/tests/tests/security/res/raw/sig_rsa_2048.bin diff --git a/tests/tests/security/res/raw/sig_sdk_sandbox.bin b/tests/tests/security/res/raw/sig_sdk_sandbox.bin Binary files differnew file mode 100644 index 00000000000..3b01c2e3e88 --- /dev/null +++ b/tests/tests/security/res/raw/sig_sdk_sandbox.bin diff --git a/tests/tests/security/res/raw/sig_security_cts_test_cert.bin b/tests/tests/security/res/raw/sig_security_cts_test_cert.bin Binary files differnew file mode 100644 index 00000000000..e62cbffbb7b --- /dev/null +++ b/tests/tests/security/res/raw/sig_security_cts_test_cert.bin diff --git a/tests/tests/security/res/raw/sig_shell_as_test_app_key.bin b/tests/tests/security/res/raw/sig_shell_as_test_app_key.bin Binary files differnew file mode 100644 index 00000000000..be5aab59f1b --- /dev/null +++ b/tests/tests/security/res/raw/sig_shell_as_test_app_key.bin diff --git a/tests/tests/security/res/raw/sig_test_cert_1.bin b/tests/tests/security/res/raw/sig_test_cert_1.bin Binary files differnew file mode 100644 index 00000000000..e611e3d9383 --- /dev/null +++ b/tests/tests/security/res/raw/sig_test_cert_1.bin diff --git a/tests/tests/security/res/raw/sig_test_cert_2.bin b/tests/tests/security/res/raw/sig_test_cert_2.bin Binary files differnew file mode 100644 index 00000000000..7723beab571 --- /dev/null +++ b/tests/tests/security/res/raw/sig_test_cert_2.bin diff --git a/tests/tests/security/res/raw/sig_test_cert_3.bin b/tests/tests/security/res/raw/sig_test_cert_3.bin Binary files differnew file mode 100644 index 00000000000..cc82af9c031 --- /dev/null +++ b/tests/tests/security/res/raw/sig_test_cert_3.bin diff --git a/tests/tests/security/res/raw/sig_test_cert_4.bin b/tests/tests/security/res/raw/sig_test_cert_4.bin Binary files differnew file mode 100644 index 00000000000..29980db219a --- /dev/null +++ b/tests/tests/security/res/raw/sig_test_cert_4.bin diff --git a/tests/tests/security/res/raw/sig_testcert.bin b/tests/tests/security/res/raw/sig_testcert.bin Binary files differnew file mode 100644 index 00000000000..cae337ec07c --- /dev/null +++ b/tests/tests/security/res/raw/sig_testcert.bin diff --git a/tests/tests/security/res/raw/sig_unit_test.bin b/tests/tests/security/res/raw/sig_unit_test.bin Binary files differnew file mode 100644 index 00000000000..4dbbc4946bd --- /dev/null +++ b/tests/tests/security/res/raw/sig_unit_test.bin diff --git a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java index 92f3706c989..f9ce8df71b5 100644 --- a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java +++ b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -44,7 +45,7 @@ public class PackageSignatureTest extends AndroidTestCase { @RestrictedBuildTest public void testPackageSignatures() throws Exception { - Set<String> badPackages = new HashSet<String>(); + Set<String> badPackages = new TreeSet<>(); Set<Signature> wellKnownSignatures = getWellKnownSignatures(); PackageManager packageManager = mContext.getPackageManager(); @@ -77,7 +78,7 @@ public class PackageSignatureTest extends AndroidTestCase { * .bin files when adding entries to this list. */ private Set<Signature> getWellKnownSignatures() throws NotFoundException, IOException { - Set<Signature> wellKnownSignatures = new HashSet<Signature>(); + Set<Signature> wellKnownSignatures = new HashSet<>(); wellKnownSignatures.add(getSignature(R.raw.sig_media)); wellKnownSignatures.add(getSignature(R.raw.sig_platform)); wellKnownSignatures.add(getSignature(R.raw.sig_shared)); @@ -107,6 +108,83 @@ public class PackageSignatureTest extends AndroidTestCase { // won't negatively affect tests to include their signatures here too. wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata)); wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata2)); + wellKnownSignatures.add(getSignature(R.raw.sig_android_telephony_cts_testkey)); + wellKnownSignatures.add(getSignature(R.raw.sig_build_bazel_examples_apex_minimal)); + wellKnownSignatures.add(getSignature(R.raw.sig_cert)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_adbd)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_adservices)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_adservices_api)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_appsearch)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_art)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_bluetooth)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_btservices)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_car_framework)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_cellbroadcast)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_compos)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_connectivity_resources)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_extservices)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_geotz)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_hardware_core_permissions)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_hardware_power)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_hardware_sensors)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_hardware_thermal)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_hardware_usb)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_hardware_vibrator)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_hardware_wifi)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_hotspot2_osulogin)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_i18n)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_ipsec)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_mediaprovider)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_neuralnetworks)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_os_statsd)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_permission)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_rkpd)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_runtime)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_safetycenter_resources)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_sdkext)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_uwb)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_uwb_resources)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_vibrator_drv2624)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_vibrator_sunfish)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_virt)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_vndk)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_wifi)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_wifi_dialog)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_android_wifi_resources)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_google_cf_apex)); + wellKnownSignatures.add(getSignature(R.raw.sig_com_google_emulated_camera_provider_hal)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_appsearch_helper_cert_a)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_appsearch_helper_cert_b)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_appsearch_hosttest_helper_cert_a)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_appsearch_hosttest_helper_cert_b)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_blob_helper_cert)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_blob_helper_cert2)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_keyset_test_a)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_keyset_test_b)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_keyset_test_c)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_keyset_test_ec_a)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_net_app)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_testkey1)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_testkey2)); + wellKnownSignatures.add(getSignature(R.raw.sig_cts_uicc_2021)); + wellKnownSignatures.add(getSignature(R.raw.sig_ec_p256)); + wellKnownSignatures.add(getSignature(R.raw.sig_ec_p256_2)); + wellKnownSignatures.add(getSignature(R.raw.sig_ec_p256_3)); + wellKnownSignatures.add(getSignature(R.raw.sig_ec_p256_4)); + wellKnownSignatures.add(getSignature(R.raw.sig_ec_p256_5)); + wellKnownSignatures.add(getSignature(R.raw.sig_keyset_A)); + wellKnownSignatures.add(getSignature(R.raw.sig_keyset_B)); + wellKnownSignatures.add(getSignature(R.raw.sig_rro_remounted_test_a)); + wellKnownSignatures.add(getSignature(R.raw.sig_rsa_2048)); + wellKnownSignatures.add(getSignature(R.raw.sig_sdk_sandbox)); + wellKnownSignatures.add(getSignature(R.raw.sig_security_cts_test_cert)); + wellKnownSignatures.add(getSignature(R.raw.sig_shell_as_test_app_key)); + wellKnownSignatures.add(getSignature(R.raw.sig_test_cert_1)); + wellKnownSignatures.add(getSignature(R.raw.sig_test_cert_2)); + wellKnownSignatures.add(getSignature(R.raw.sig_test_cert_3)); + wellKnownSignatures.add(getSignature(R.raw.sig_test_cert_4)); + wellKnownSignatures.add(getSignature(R.raw.sig_testcert)); + wellKnownSignatures.add(getSignature(R.raw.sig_unit_test)); return wellKnownSignatures; } diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java index dae46f7fb61..78fb0af896c 100644 --- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java +++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java @@ -147,6 +147,12 @@ public class TelephonyManagerTestOnMockModem { sCallStateChangeCallbackHandler = new Handler(sCallStateChangeCallbackHandlerThread.getLooper()); mShaId = getShaId(TelephonyUtils.CTS_APP_PACKAGE); + + final PackageManager pm = getContext().getPackageManager(); + if (pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) { + //Wait for Telephony FW and RIL initialization are done + TimeUnit.SECONDS.sleep(4); + } } @AfterClass diff --git a/tests/tests/textclassifier/res/layout/main.xml b/tests/tests/textclassifier/res/layout/main.xml index fe6f06bad20..73ab09943b1 100644 --- a/tests/tests/textclassifier/res/layout/main.xml +++ b/tests/tests/textclassifier/res/layout/main.xml @@ -22,7 +22,7 @@ <TextView android:id="@+id/textview" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
\ No newline at end of file diff --git a/tests/tests/view/src/android/view/cts/PrecompiledLayoutTest.java b/tests/tests/view/src/android/view/cts/PrecompiledLayoutTest.java deleted file mode 100644 index 2bc05d3ad1c..00000000000 --- a/tests/tests/view/src/android/view/cts/PrecompiledLayoutTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.cts; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.test.InstrumentationRegistry; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -public class PrecompiledLayoutTest { - private LayoutInflater mInflater; - - @Before - public void setup() { - Context context = InstrumentationRegistry.getTargetContext(); - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - /** - * Check whether two layouts are equivalent. We do this by making sure the - * classes match, that they have the same ID, and that ViewGroups have the - * same children. - */ - private void assertEquivalentLayouts(View lhs, View rhs) { - // Make sure both are null or both are not null. - Assert.assertEquals(lhs == null, rhs == null); - if (lhs == null) { - // If the two objects are null, then they are the same. - return; - } - - // The two views should have the same class and ID - Assert.assertSame(lhs.getClass(), rhs.getClass()); - Assert.assertEquals(lhs.getId(), rhs.getId()); - - // If these are ViewGroups, make sure the children are also equivalent. - if (lhs instanceof ViewGroup) { - ViewGroup lhsGroup = (ViewGroup) lhs; - ViewGroup rhsGroup = (ViewGroup) rhs; - - Assert.assertEquals(lhsGroup.getChildCount(), rhsGroup.getChildCount()); - - for (int i = 0; i < lhsGroup.getChildCount(); i++) { - assertEquivalentLayouts(lhsGroup.getChildAt(i), rhsGroup.getChildAt(i)); - } - } - } - - private void compareInflation(int resourceId) { - // Inflate without layout precompilation. - mInflater.setPrecompiledLayoutsEnabledForTesting(false); - View interpreted = mInflater.inflate(resourceId, null); - - // Inflate with layout precompilation - mInflater.setPrecompiledLayoutsEnabledForTesting(true); - View precompiled = mInflater.inflate(resourceId, null); - - assertEquivalentLayouts(interpreted, precompiled); - } - - // The following tests make sure that we get equivalent layouts when - // inflated with and without precompilation. - - @Test - public void equivalentInflaterLayout() { - compareInflation(R.layout.inflater_layout); - } - - @Test - public void equivalentInflaterOverrideThemeLayout() { - compareInflation(R.layout.inflater_override_theme_layout); - } - - @Test - public void equivalentInflaterLayoutTags() { - compareInflation(R.layout.inflater_layout_tags); - } -} diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/ActivityInterceptionTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/ActivityInterceptionTest.java index fcf7d1ad365..fd5fad5bd04 100644 --- a/tests/tests/virtualdevice/src/android/virtualdevice/cts/ActivityInterceptionTest.java +++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/ActivityInterceptionTest.java @@ -165,7 +165,8 @@ public class ActivityInterceptionTest { mInterceptor); // Starting test on EmptyActivity - Intent startIntent = new Intent(mContext, EmptyActivity.class) + Intent startIntent = new Intent(Intent.ACTION_MAIN) + .setComponent(new ComponentName(mContext, EmptyActivity.class)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); EmptyActivity emptyActivity = (EmptyActivity) InstrumentationRegistry.getInstrumentation() @@ -212,7 +213,8 @@ public class ActivityInterceptionTest { interceptorOther); // Starting test on EmptyActivity - Intent startIntent = new Intent(mContext, EmptyActivity.class) + Intent startIntent = new Intent(Intent.ACTION_MAIN) + .setComponent(new ComponentName(mContext, EmptyActivity.class)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -260,7 +262,8 @@ public class ActivityInterceptionTest { mInterceptor); // Starting test on EmptyActivity - Intent startIntent = new Intent(mContext, EmptyActivity.class) + Intent startIntent = new Intent(Intent.ACTION_MAIN) + .setComponent(new ComponentName(mContext, EmptyActivity.class)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -301,7 +304,8 @@ public class ActivityInterceptionTest { Executors.newSingleThreadExecutor(), interceptorOther); // Starting test on EmptyActivity - Intent startIntent = new Intent(mContext, EmptyActivity.class) + Intent startIntent = new Intent(Intent.ACTION_MAIN) + .setComponent(new ComponentName(mContext, EmptyActivity.class)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualDeviceManagerBasicTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualDeviceManagerBasicTest.java index ff8ea3fb313..1ddd14cac56 100644 --- a/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualDeviceManagerBasicTest.java +++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualDeviceManagerBasicTest.java @@ -132,7 +132,7 @@ public class VirtualDeviceManagerBasicTest { @After public void tearDown() { - if (mVirtualDevice != null && mVirtualDevice.getDeviceId() != DEVICE_ID_INVALID) { + if (mVirtualDevice != null) { mVirtualDevice.close(); } if (mAnotherVirtualDevice != null) { @@ -194,7 +194,9 @@ public class VirtualDeviceManagerBasicTest { mVirtualDevice.close(); - assertThat(mVirtualDevice.getDeviceId()).isEqualTo(DEVICE_ID_INVALID); + // Ensure the virtual device can no longer setup new functionality + assertThrows(SecurityException.class, () -> mVirtualDevice.createVirtualDisplay( + DEFAULT_VIRTUAL_DISPLAY_CONFIG, null, null)); } @Test @@ -207,7 +209,9 @@ public class VirtualDeviceManagerBasicTest { mVirtualDevice.close(); mVirtualDevice.close(); - assertThat(mVirtualDevice.getDeviceId()).isEqualTo(DEVICE_ID_INVALID); + // Ensure the virtual device can no longer setup new functionality + assertThrows(SecurityException.class, () -> mVirtualDevice.createVirtualDisplay( + DEFAULT_VIRTUAL_DISPLAY_CONFIG, null, null)); } @Test @@ -256,7 +260,6 @@ public class VirtualDeviceManagerBasicTest { // Ensure device is closed properly and the display is removed assertThat(display.getDisplay().isValid()).isFalse(); - assertThat(mVirtualDevice.getDeviceId()).isEqualTo(DEVICE_ID_INVALID); // Ensure the virtual device can no longer setup new functionality assertThrows(SecurityException.class, () -> mVirtualDevice.createVirtualDisplay( @@ -302,7 +305,9 @@ public class VirtualDeviceManagerBasicTest { mVirtualDevice.close(); - assertThat(mVirtualDevice.getDeviceId()).isEqualTo(DEVICE_ID_INVALID); + // Ensure the virtual device can no longer setup new functionality + assertThrows(SecurityException.class, () -> mVirtualDevice.createVirtualDisplay( + DEFAULT_VIRTUAL_DISPLAY_CONFIG, null, null)); } @Test @@ -343,7 +348,9 @@ public class VirtualDeviceManagerBasicTest { mFakeAssociationRule.disassociate(); latch.await(5, TimeUnit.SECONDS); - assertThat(mVirtualDevice.getDeviceId()).isEqualTo(DEVICE_ID_INVALID); + // Ensure the virtual device can no longer setup new functionality + assertThrows(SecurityException.class, () -> mVirtualDevice.createVirtualDisplay( + DEFAULT_VIRTUAL_DISPLAY_CONFIG, null, null)); } @Test @@ -384,7 +391,9 @@ public class VirtualDeviceManagerBasicTest { mVirtualDevice.close(); latch.await(5, TimeUnit.SECONDS); - assertThat(mVirtualDevice.getDeviceId()).isEqualTo(DEVICE_ID_INVALID); + // Ensure the virtual device can no longer setup new functionality + assertThrows(SecurityException.class, () -> mVirtualDevice.createVirtualDisplay( + DEFAULT_VIRTUAL_DISPLAY_CONFIG, null, null)); } /** @@ -865,4 +874,3 @@ public class VirtualDeviceManagerBasicTest { } } } - diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/sensor/VirtualSensorTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/sensor/VirtualSensorTest.java index 005eab1e159..ef720f868a5 100644 --- a/tests/tests/virtualdevice/src/android/virtualdevice/cts/sensor/VirtualSensorTest.java +++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/sensor/VirtualSensorTest.java @@ -97,7 +97,7 @@ public class VirtualSensorTest { private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer"; private static final String VIRTUAL_SENSOR_VENDOR = "VirtualDeviceVendor"; - private static final int CUSTOM_SENSOR_TYPE = 9999; + private static final int CUSTOM_SENSOR_TYPE = Sensor.TYPE_DEVICE_PRIVATE_BASE + 9999; private static final int SENSOR_TIMEOUT_MILLIS = 1000; diff --git a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java index 74fdf642e09..a23a8904b4f 100644 --- a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java +++ b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java @@ -680,8 +680,8 @@ public class SingleDeviceTest extends WifiJUnit3TestBase { Characteristics characteristics = mWifiAwareManager.getCharacteristics(); assertNotNull("Wi-Fi Aware characteristics are null", characteristics); assertEquals("Service Name Length", characteristics.getMaxServiceNameLength(), 255); - assertEquals("Service Specific Information Length", - characteristics.getMaxServiceSpecificInfoLength(), 255); + assertTrue("Service Specific Information Length", + characteristics.getMaxServiceSpecificInfoLength() >= 255); assertEquals("Match Filter Length", characteristics.getMaxMatchFilterLength(), 255); assertNotEquals("Cipher suites", characteristics.getSupportedCipherSuites(), 0); assertTrue("Max number of NDP", characteristics.getNumberOfSupportedDataPaths() > 0); diff --git a/tools/cts-tradefed/Android.bp b/tools/cts-tradefed/Android.bp index b699bdec0f4..9fa7ac21fc7 100644 --- a/tools/cts-tradefed/Android.bp +++ b/tools/cts-tradefed/Android.bp @@ -34,7 +34,7 @@ tradefed_binary_host { wrapper: "etc/cts-tradefed", short_name: "CTS", full_name: "Compatibility Test Suite", - version: "14_r2", + version: "14_r3", static_libs: ["cts-tradefed-harness"], required: ["compatibility-host-util"], } diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml index 0ad645eab21..ee57f286d9f 100644 --- a/tools/cts-tradefed/res/config/cts-known-failures.xml +++ b/tools/cts-tradefed/res/config/cts-known-failures.xml @@ -308,4 +308,16 @@ <!-- b/303631162 --> <option name="compatibility:exclude-filter" value="CtsTelephonyTestCases android.telephony.ims.cts.ImsCallingTest#testCallJoinExistingConferenceCallAfterCallSwap" /> <option name="compatibility:exclude-filter" value="CtsTelephonyTestCases android.telephony.ims.cts.ImsCallingTest#testCallJoinExistingConferenceCallAfterCallSwapFail" /> + + <!-- b/294251187 --> + <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.SurfaceControlViewHostTests#testFocusWithTouch" /> + <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.SurfaceControlViewHostTests#testPopupWindowPosition" /> + <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.SurfaceControlViewHostTests#testFocusWithTouchCrossProcess" /> + <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.SurfaceControlViewHostTests#testChildWindowFocusable" /> + + <!-- b/294253316 --> + <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.WindowUntrustedTouchTest#testWhenOneCustomToastWindowAndOneSawWindowBelowThreshold_blocksTouch" /> + + <!-- b/307489638 --> + <option name="compatibility:exclude-filter" value="CtsTelephonyTestCases android.telephony.satellite.cts.SatelliteManagerTestOnMockService" /> </configuration> diff --git a/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml b/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml index dc93565805c..462b477b893 100644 --- a/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml +++ b/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml @@ -104,7 +104,6 @@ <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.ActivityManagerTest#testTimeTrackingAPI_SwitchAwayTriggers" /> <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.BooleanTileServiceTest" /> <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.TileServiceTest" /> - <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.QuietModeHostsideTest" /> <option name="compatibility:exclude-filter" value="CtsShortcutManagerLauncher1" /> <option name="compatibility:exclude-filter" value="CtsShortcutManagerLauncher2" /> <option name="compatibility:exclude-filter" value="CtsShortcutManagerLauncher3" /> |