[automerger skipped] Merge "Updated CTS test for Android Security b/194695497" into qt-dev am: bb5d454f74 am: 96299b6712 -s ours am: b9c55aacd9 -s ours am: 2810bb870f -s ours am: c343888025 -s ours
am skip reason: Merged-In I0c761f90717188c623a4fbc492e0d1a130c18853 with SHA-1 365879ef3f is already in history
Original change: https://googleplex-android-review.googlesource.com/c/platform/cts/+/18310610
Change-Id: I9233172c6415be6b57ceb37b6d4b22c750f5fc56
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
index 495c3ef..fe314c42 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
@@ -23,7 +23,6 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
-import android.util.Log;
import com.android.compatibility.common.util.FileUtil;
import com.android.compatibility.common.util.IInvocationResult;
@@ -50,8 +49,6 @@
* Background task to generate a report and save it to external storage.
*/
class ReportExporter extends AsyncTask<Void, Void, String> {
- private static final String TAG = ReportExporter.class.getSimpleName();
- private static final boolean DEBUG = true;
public static final String REPORT_DIRECTORY = "VerifierReports";
public static final String LOGS_DIRECTORY = "ReportLogFiles";
@@ -79,15 +76,11 @@
// so that they will get ZIPped into the transmitted file.
//
private void copyReportFiles(File tempDir) {
- if (DEBUG) {
- Log.d(TAG, "copyReportFiles(" + tempDir.getAbsolutePath() + ")");
- }
-
File externalStorageDirectory = Environment.getExternalStorageDirectory();
File reportLogFolder =
new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator
- + LOGS_DIRECTORY);
+ + REPORT_DIRECTORY);
File[] reportLogFiles = reportLogFolder.listFiles();
// if no ReportLog files have been created (i.e. the folder doesn't exist)
@@ -163,9 +156,6 @@
}
private void saveReportOnInternalStorage(File reportZipFile) {
- if (DEBUG) {
- Log.d(TAG, "---- saveReportOnInternalStorage(" + reportZipFile.getAbsolutePath() + ")");
- }
try {
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
reportZipFile, ParcelFileDescriptor.MODE_READ_ONLY);
diff --git a/apps/hotspot/AndroidManifest.xml b/apps/hotspot/AndroidManifest.xml
index e0db7be..09c494f 100644
--- a/apps/hotspot/AndroidManifest.xml
+++ b/apps/hotspot/AndroidManifest.xml
@@ -6,7 +6,6 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application>
<activity android:name=".MainActivity"
android:exported="true">
diff --git a/apps/hotspot/src/com/android/cts/hotspot/MainActivity.java b/apps/hotspot/src/com/android/cts/hotspot/MainActivity.java
index eb81563..2e0ed87 100644
--- a/apps/hotspot/src/com/android/cts/hotspot/MainActivity.java
+++ b/apps/hotspot/src/com/android/cts/hotspot/MainActivity.java
@@ -16,11 +16,5 @@
ActivityCompat.requestPermissions(
this, new String[] {Manifest.permission.ACCESS_COARSE_LOCATION}, 2);
}
-
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
- != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(
- this, new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, 2);
- }
}
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
index af75f79..7966e20 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
@@ -17,6 +17,11 @@
package com.android.bedstead.nene.packages;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.content.pm.PackageInstaller.EXTRA_STATUS;
+import static android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE;
+import static android.content.pm.PackageInstaller.STATUS_FAILURE;
+import static android.content.pm.PackageInstaller.STATUS_SUCCESS;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
import static android.os.Build.VERSION.SDK_INT;
import static com.android.bedstead.nene.users.User.UserState.RUNNING_UNLOCKED;
@@ -27,6 +32,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
+import android.util.Log;
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
@@ -45,8 +51,6 @@
import com.android.bedstead.nene.utils.Versions;
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
-import com.google.common.io.Files;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -56,6 +60,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Test APIs relating to packages.
@@ -207,13 +212,6 @@
return install(user, loadBytes(apkFile));
}
- User resolvedUser = user.resolve();
-
- if (resolvedUser == null || resolvedUser.state() != RUNNING_UNLOCKED) {
- throw new NeneException("Packages can not be installed in non-started users "
- + "(Trying to install into user " + resolvedUser + ")");
- }
-
BlockingBroadcastReceiver broadcastReceiver =
registerPackageInstalledBroadcastReceiver(user);
@@ -265,111 +263,59 @@
* <p>If the package is already installed, this will replace it.
*
* <p>If the package is marked testOnly, it will still be installed.
+ *
+ * <p>When running as an instant app, this will return null. On other versions it will return
+ * the installed package.
*/
public PackageReference install(UserReference user, byte[] apkFile) {
if (user == null || apkFile == null) {
throw new NullPointerException();
}
- if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
- return installPreS(user, apkFile);
- }
+ try {
+ ShellCommand.builderForUser(user, "pm install")
+ .addOperand("-t") // Allow installing test apks
+ .addOperand("-r") // Replace existing apps
+ .addOption("-S", apkFile.length) // Install from stdin
+ .writeToStdIn(apkFile)
+ .validate(ShellCommandUtils::startsWithSuccess)
+ .execute();
+ } catch (AdbException e) {
+ throw new NeneException("Error installing from instant app", e);
+ }
- User resolvedUser = user.resolve();
+ // Arbitrary sleep because the shell command doesn't block and we can't listen for
+ // the broadcast (instant app)
+ try {
+ Thread.sleep(10_000);
+ } catch (InterruptedException e) {
+ throw new NeneException("Interrupted while waiting for install", e);
+ }
- if (resolvedUser == null || resolvedUser.state() != RUNNING_UNLOCKED) {
- throw new NeneException("Packages can not be installed in non-started users "
- + "(Trying to install into user " + resolvedUser + ")");
- }
+ return null;
+ }
+ @Nullable
+ private PackageReference installPreS(UserReference user, byte[] apkFile) {
+ // This is not in the try because if the install fails we don't want to await the broadcast
BlockingBroadcastReceiver broadcastReceiver =
registerPackageInstalledBroadcastReceiver(user);
+
+ // We should install using stdin with the byte array
try {
- // Expected output "Success"
ShellCommand.builderForUser(user, "pm install")
- .addOption("-S", apkFile.length)
- .addOperand("-r")
- .addOperand("-t")
+ .addOperand("-t") // Allow installing test apks
+ .addOperand("-r") // Replace existing apps
+ .addOption("-S", apkFile.length) // Install from stdin
.writeToStdIn(apkFile)
.validate(ShellCommandUtils::startsWithSuccess)
.execute();
-
return waitForPackageAddedBroadcast(broadcastReceiver);
} catch (AdbException e) {
- throw new NeneException("Could not install from bytes for user " + user, e);
+ throw new NeneException("Error installing package", e);
} finally {
- broadcastReceiver.unregisterQuietly();
- }
-
- // TODO(scottjonathan): Re-enable this after we have a TestAPI which allows us to install
- // testOnly apks
-// BlockingBroadcastReceiver broadcastReceiver =
-// registerPackageInstalledBroadcastReceiver(user);
-//
-// PackageManager packageManager =
-// mTestApis.context().androidContextAsUser(user).getPackageManager();
-// PackageInstaller packageInstaller = packageManager.getPackageInstaller();
-//
-// try {
-// int sessionId;
-// try(PermissionContext p =
-// mTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
-// PackageInstaller.SessionParams sessionParams =
-// new PackageInstaller.SessionParams(MODE_FULL_INSTALL);
-// // TODO(scottjonathan): Enable installing test apps once there is a test
-// // API for this
-//// sessionParams.installFlags =
-// sessionParams.installFlags | INSTALL_ALLOW_TEST;
-// sessionId = packageInstaller.createSession(sessionParams);
-// }
-//
-// PackageInstaller.Session session = packageInstaller.openSession(sessionId);
-// try (OutputStream out =
-// session.openWrite("NAME", 0, apkFile.length)) {
-// out.write(apkFile);
-// session.fsync(out);
-// }
-//
-// try (BlockingIntentSender intentSender = BlockingIntentSender.create()) {
-// try (PermissionContext p =
-// mTestApis.permissions().withPermission(INSTALL_PACKAGES)) {
-// session.commit(intentSender.intentSender());
-// session.close();
-// }
-//
-// Intent intent = intentSender.await();
-// if (intent.getIntExtra(EXTRA_STATUS, /* defaultValue= */ STATUS_FAILURE)
-// != STATUS_SUCCESS) {
-// throw new NeneException("Not successful while installing package. "
-// + "Got status: "
-// + intent.getIntExtra(
-// EXTRA_STATUS, /* defaultValue= */ STATUS_FAILURE)
-// + " exta info: " + intent.getStringExtra(EXTRA_STATUS_MESSAGE));
-// }
-// }
-//
-// return waitForPackageAddedBroadcast(broadcastReceiver);
-// } catch (IOException e) {
-// throw new NeneException("Could not install package", e);
-// } finally {
-// broadcastReceiver.unregisterQuietly();
-// }
- }
-
- private PackageReference installPreS(UserReference user, byte[] apkFile) {
- // Prior to S we cannot pass bytes to stdin so we write it to a temp file first
- File outputDir = mTestApis.context().instrumentedContext().getCacheDir();
- File outputFile = null;
- try {
- outputFile = File.createTempFile("tmp", ".apk", outputDir);
- Files.write(apkFile, outputFile);
- outputFile.setReadable(true, false);
- return install(user, outputFile);
- } catch (IOException e) {
- throw new NeneException("Error when writing bytes to temp file", e);
- } finally {
- if (outputFile != null) {
- outputFile.delete();
+ if (broadcastReceiver != null) {
+ broadcastReceiver.unregisterQuietly();
}
}
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
index e61fed0..3a624f3 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
@@ -83,10 +83,7 @@
logCommand(command, allowEmptyOutput, stdInBytes);
if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
- if (stdInBytes != null && stdInBytes.length > 0) {
- throw new IllegalStateException("Cannot write to stdIn prior to S");
- }
- return executeCommandPreS(command, allowEmptyOutput);
+ return executeCommandPreS(command, allowEmptyOutput, stdInBytes);
}
// TODO(scottjonathan): Add argument to force errors to stderr
@@ -124,11 +121,7 @@
logCommand(command, /* allowEmptyOutput= */ false, stdInBytes);
if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
- if (stdInBytes != null && stdInBytes.length > 0) {
- throw new IllegalStateException("Cannot write to stdIn prior to S");
- }
-
- return executeCommandForBytesPreS(command);
+ return executeCommandForBytesPreS(command, stdInBytes);
}
// TODO(scottjonathan): Add argument to force errors to stderr
@@ -217,10 +210,14 @@
}
private static String executeCommandPreS(
- String command, boolean allowEmptyOutput) throws AdbException {
- ParcelFileDescriptor fdOut = uiAutomation().executeShellCommand(command);
+ String command, boolean allowEmptyOutput, byte[] stdIn) throws AdbException {
+ ParcelFileDescriptor[] fds = uiAutomation().executeShellCommandRw(command);
+ ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX];
+ ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX];
try {
+ writeStdInAndClose(fdIn, stdIn);
+
try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) {
String out = new String(FileUtils.readInputStreamFully(fis));
@@ -242,10 +239,17 @@
}
}
- private static byte[] executeCommandForBytesPreS(String command) throws AdbException {
- ParcelFileDescriptor fdOut = uiAutomation().executeShellCommand(command);
+ // This is warned for executeShellCommandRw which did exist as TestApi
+ @SuppressWarnings("NewApi")
+ private static byte[] executeCommandForBytesPreS(
+ String command, byte[] stdInBytes) throws AdbException {
+ ParcelFileDescriptor[] fds = uiAutomation().executeShellCommandRw(command);
+ ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX];
+ ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX];
try {
+ writeStdInAndClose(fdIn, stdInBytes);
+
try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) {
return FileUtils.readInputStreamFully(fis);
}
diff --git a/tests/tests/netpermission/updatestatspermission/Android.bp b/hostsidetests/appcloning/Android.bp
similarity index 60%
copy from tests/tests/netpermission/updatestatspermission/Android.bp
copy to hostsidetests/appcloning/Android.bp
index 7a24886..f1ecc4b 100644
--- a/tests/tests/netpermission/updatestatspermission/Android.bp
+++ b/hostsidetests/appcloning/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,18 +16,23 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test {
- name: "CtsNetTestCasesUpdateStatsPermission",
- defaults: ["cts_defaults"],
-
- srcs: ["src/**/*.java"],
-
- static_libs: ["ctstestrunner-axt"],
-
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
+java_test_host {
+ name: "CtsAppCloningHostTest",
+ srcs: [
+ "hostside/src/**/AppCloningHostTest.java",
+ "hostside/src/**/BaseHostTestCase.java",
],
-
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "testng",
+ ],
+ // tag the module as cts a test artifact
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
+ test_config: "AndroidTestAppCloning.xml",
+ data: [":CtsAppCloningTestApp"],
}
diff --git a/hostsidetests/appcloning/AndroidTestAppCloning.xml b/hostsidetests/appcloning/AndroidTestAppCloning.xml
new file mode 100644
index 0000000..8c882eb
--- /dev/null
+++ b/hostsidetests/appcloning/AndroidTestAppCloning.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Test for App cloning support with clone user profiles">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <!-- Clone user profile is meant to exist only alongside a real system user.
+ It does not exist for a headless system user, or a secondary user -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="com.android.cts.appcloning.AppCloningHostTest" />
+ </test>
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+ </object>
+</configuration>
diff --git a/hostsidetests/appcloning/OWNERS b/hostsidetests/appcloning/OWNERS
new file mode 100644
index 0000000..7cc9314
--- /dev/null
+++ b/hostsidetests/appcloning/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1029024
+saumyap@google.com
+maco@google.com
+dagarhimanshu@google.com
+sarup@google.com
+sailendrabathi@google.com
\ No newline at end of file
diff --git a/hostsidetests/appcloning/TEST_MAPPING b/hostsidetests/appcloning/TEST_MAPPING
new file mode 100644
index 0000000..f512df9
--- /dev/null
+++ b/hostsidetests/appcloning/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsAppCloningHostTest"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
similarity index 77%
rename from hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java
rename to hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
index 281df8f..ce7356c5 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java
+++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.scopedstorage.cts.host;
+package com.android.cts.appcloning;
import static com.google.common.truth.Truth.assertThat;
@@ -40,34 +40,45 @@
@RunWith(DeviceJUnit4ClassRunner.class)
@AppModeFull
public class AppCloningHostTest extends BaseHostTestCase {
- private static final String APP_A = "CtsScopedStorageTestAppA.apk";
- private static final String APP_A_PACKAGE = "android.scopedstorage.cts.testapp.A.withres";
+
+ private static final String APP_A = "CtsAppCloningTestApp.apk";
+ private static final String APP_A_PACKAGE = "com.android.cts.appcloningtestapp";
+
private static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000;
+
private String mCloneUserId;
private ContentProviderHandler mContentProviderHandler;
-
@Before
public void setup() throws Exception {
assumeFalse("Device is in headless system user mode", isHeadlessSystemUserMode());
assumeTrue(isAtLeastS());
+ assumeFalse("Device uses sdcardfs", usesSdcardFs());
+ // create clone user
String output = executeShellCommand(
"pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE "
+ "testUser");
- mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]", "");
+ mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]",
+ "");
assertThat(mCloneUserId).isNotEmpty();
+
CommandResult out = executeShellV2Command("am start-user -w %s", mCloneUserId);
assertThat(out.getStderr()).isEmpty();
+
mContentProviderHandler = new ContentProviderHandler(getDevice());
mContentProviderHandler.setUp();
}
@After
public void tearDown() throws Exception {
- if (isHeadlessSystemUserMode() || !isAtLeastS()) return;
- mContentProviderHandler.tearDown();
+ if (isHeadlessSystemUserMode() || !isAtLeastS() || usesSdcardFs()) return;
+ if (mContentProviderHandler != null) {
+ mContentProviderHandler.tearDown();
+ }
+
+ // remove the clone user
executeShellCommand("pm remove-user %s", mCloneUserId);
}
@@ -86,7 +97,8 @@
eventually(() -> {
// Wait for finish.
assertThat(isSuccessful(
- runContentProviderCommand("query", mCloneUserId, "/sdcard", ""))).isTrue();
+ runContentProviderCommand("query", mCloneUserId,
+ "/sdcard", ""))).isTrue();
}, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
// Create a file on the clone user storage
@@ -95,16 +107,19 @@
eventually(() -> {
// Wait for finish.
assertThat(isSuccessful(
- runContentProviderCommand("write", mCloneUserId, "/sdcard/testFile.txt",
+ runContentProviderCommand("write", mCloneUserId,
+ "/sdcard/testFile.txt",
"< /sdcard/testFile.txt"))).isTrue();
}, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
// Check that the above created file exists on the clone user storage
- out = runContentProviderCommand("query", mCloneUserId, "/sdcard/testFile.txt", "");
+ out = runContentProviderCommand("query", mCloneUserId,
+ "/sdcard/testFile.txt", "");
assertThat(isSuccessful(out)).isTrue();
// Cleanup the created file
- out = runContentProviderCommand("delete", mCloneUserId, "/sdcard/testFile.txt", "");
+ out = runContentProviderCommand("delete", mCloneUserId,
+ "/sdcard/testFile.txt", "");
assertThat(isSuccessful(out)).isTrue();
}
@@ -129,4 +144,15 @@
commandType, userId, fullUri, args);
}
+ private boolean usesSdcardFs() throws Exception {
+ CommandResult out = executeShellV2Command("cat /proc/mounts");
+ assertThat(isSuccessful(out)).isTrue();
+ for (String line : out.getStdout().split("\n")) {
+ String[] split = line.split(" ");
+ if (split.length >= 3 && split[2].equals("sdcardfs")) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java
new file mode 100644
index 0000000..3d301f1
--- /dev/null
+++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appcloning;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.NativeDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+
+abstract class BaseHostTestCase extends BaseHostJUnit4Test {
+ private int mCurrentUserId = NativeDevice.INVALID_USER_ID;
+ private static final String ERROR_MESSAGE_TAG = "[ERROR]";
+
+ protected String executeShellCommand(String cmd, Object... args) throws Exception {
+ return getDevice().executeShellCommand(String.format(cmd, args));
+ }
+
+ protected CommandResult executeShellV2Command(String cmd, Object... args) throws Exception {
+ return getDevice().executeShellV2Command(String.format(cmd, args));
+ }
+
+ protected boolean isPackageInstalled(String packageName, String userId) throws Exception {
+ return getDevice().isPackageInstalled(packageName, userId);
+ }
+
+ // TODO (b/174775905) remove after exposing the check from ITestDevice.
+ protected boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException {
+ String result = getDevice()
+ .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim();
+ return "true".equalsIgnoreCase(result);
+ }
+
+ protected boolean isAtLeastS() throws DeviceNotAvailableException {
+ return getDevice().getApiLevel() >= 31 /* BUILD.VERSION_CODES.S */;
+ }
+
+ protected static void eventually(ThrowingRunnable r, long timeoutMillis) {
+ long start = System.currentTimeMillis();
+
+ while (true) {
+ try {
+ r.run();
+ return;
+ } catch (Throwable e) {
+ if (System.currentTimeMillis() - start < timeoutMillis) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignored) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ protected int getCurrentUserId() throws Exception {
+ setCurrentUserId();
+
+ return mCurrentUserId;
+ }
+
+ protected boolean isSuccessful(CommandResult result) {
+ if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
+ return false;
+ }
+ String stdout = result.getStdout();
+ if (stdout.contains(ERROR_MESSAGE_TAG)) {
+ return false;
+ }
+ String stderr = result.getStderr();
+ return (stderr == null || stderr.trim().isEmpty());
+ }
+
+ private void setCurrentUserId() throws Exception {
+ if (mCurrentUserId != NativeDevice.INVALID_USER_ID) return;
+
+ ITestDevice device = getDevice();
+ mCurrentUserId = device.getCurrentUser();
+ CLog.i("Current user: %d");
+ }
+
+ protected interface ThrowingRunnable {
+ /**
+ * Similar to {@link Runnable#run} but has {@code throws Exception}.
+ */
+ void run() throws Exception;
+ }
+}
diff --git a/tests/tests/netpermission/updatestatspermission/Android.bp b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
similarity index 68%
rename from tests/tests/netpermission/updatestatspermission/Android.bp
rename to hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
index 7a24886..b3edfec 100644
--- a/tests/tests/netpermission/updatestatspermission/Android.bp
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,18 +16,16 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test {
- name: "CtsNetTestCasesUpdateStatsPermission",
+android_test_helper_app {
+ name: "CtsAppCloningTestApp",
defaults: ["cts_defaults"],
-
- srcs: ["src/**/*.java"],
-
- static_libs: ["ctstestrunner-axt"],
-
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
+ static_libs: [
+ "androidx.test.rules",
+ "truth-prebuilt",
+ "cts-install-lib",
],
-
+ srcs: ["src/**/*.java"],
+ sdk_version: "test_current",
+ target_sdk_version: "current",
+ min_sdk_version: "30",
}
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml b/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..07d78a0
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.appcloningtestapp"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="30" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.appcloningtestapp" />
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java b/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java
new file mode 100644
index 0000000..46480f7
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appcloningtestapp;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AppCloningDeviceTest {
+ private static final String TAG = "AppCloningDeviceTest";
+}
diff --git a/hostsidetests/appcompat/strictjavapackages/Android.bp b/hostsidetests/appcompat/strictjavapackages/Android.bp
index fcb20e8..af734e3 100644
--- a/hostsidetests/appcompat/strictjavapackages/Android.bp
+++ b/hostsidetests/appcompat/strictjavapackages/Android.bp
@@ -24,6 +24,7 @@
"cts-tradefed",
"tradefed",
"compatibility-host-util",
+ "cts-tradefed",
],
static_libs: [
"compat-classpaths-testing",
@@ -33,6 +34,6 @@
test_suites: [
"cts",
"general-tests",
- "mts",
+ "mts-mainline-infra",
],
}
diff --git a/tests/tests/netpermission/updatestatspermission/Android.bp b/hostsidetests/appcompat/strictjavapackages/app/Android.bp
similarity index 67%
copy from tests/tests/netpermission/updatestatspermission/Android.bp
copy to hostsidetests/appcompat/strictjavapackages/app/Android.bp
index 7a24886..d96672a 100644
--- a/tests/tests/netpermission/updatestatspermission/Android.bp
+++ b/hostsidetests/appcompat/strictjavapackages/app/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,22 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
- name: "CtsNetTestCasesUpdateStatsPermission",
+android_test_helper_app {
+ name: "StrictJavaPackagesTestApp",
defaults: ["cts_defaults"],
-
srcs: ["src/**/*.java"],
-
- static_libs: ["ctstestrunner-axt"],
-
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.core",
],
-
+ sdk_version: "test_current",
+ min_sdk_version: "29",
+ test_suites: [
+ "ats",
+ "cts",
+ "gts",
+ "general-tests",
+ "mts",
+ "tvts",
+ ],
}
diff --git a/hostsidetests/appcompat/strictjavapackages/app/AndroidManifest.xml b/hostsidetests/appcompat/strictjavapackages/app/AndroidManifest.xml
new file mode 100644
index 0000000..e610581
--- /dev/null
+++ b/hostsidetests/appcompat/strictjavapackages/app/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.compat.sjp.app"
+ android:targetSandboxVersion="2">
+
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+ <application android:requestLegacyExternalStorage="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.compat.sjp.app" />
+</manifest>
diff --git a/hostsidetests/appcompat/strictjavapackages/app/src/android/compat/sjp/app/ApexDeviceTest.java b/hostsidetests/appcompat/strictjavapackages/app/src/android/compat/sjp/app/ApexDeviceTest.java
new file mode 100644
index 0000000..fa98fe6
--- /dev/null
+++ b/hostsidetests/appcompat/strictjavapackages/app/src/android/compat/sjp/app/ApexDeviceTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.compat.sjp.app;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.pm.PackageManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Device-side helper app for obtaining Apex info.
+ *
+ * <p>It is not technically a test as it simply collects information, but it simplifies the usage
+ * and communication with host-side tests.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ApexDeviceTest {
+
+ @Before
+ public void before() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity();
+ }
+
+ @After
+ public void after() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ /**
+ * Collects all apk-in-apex apps on the device and writes them to disk.
+ */
+ @Test
+ public void testCollectApkInApexPaths() throws Exception {
+ Path detailsFilepath = new File("/sdcard/apk-in-apex-paths.txt").toPath();
+ final PackageManager pm = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .getPackageManager();
+ assertNotNull("No package manager instance!", pm);
+ final Set<String> lines = pm.getInstalledPackages(0).stream()
+ .map(pkg -> pkg.applicationInfo.sourceDir)
+ .filter(sourceDir -> sourceDir != null && sourceDir.contains("/apex/"))
+ .collect(Collectors.toSet());
+ Files.write(detailsFilepath, lines);
+ }
+}
diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
index f3ba06f..4b5c310 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -20,32 +20,44 @@
import static android.compat.testing.Classpaths.ClasspathType.SYSTEMSERVERCLASSPATH;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assume.assumeTrue;
import android.compat.testing.Classpaths;
+import android.compat.testing.SharedLibraryInfo;
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.log.LogUtil.CLog;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.modules.utils.build.testing.DeviceSdkLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
-import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.jf.dexlib2.iface.ClassDef;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
+import java.util.Arrays;
import java.util.Collection;
-import java.util.HashSet;
import java.util.Set;
+import java.util.stream.Stream;
+
/**
* Tests for detecting no duplicate class files are present on BOOTCLASSPATH and
@@ -57,6 +69,18 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class StrictJavaPackagesTest extends BaseHostJUnit4Test {
+ private static final String ANDROID_TEST_MOCK_JAR = "/system/framework/android.test.mock.jar";
+ private static final String TEST_HELPER_PACKAGE = "android.compat.sjp.app";
+ private static final String TEST_HELPER_APK = "StrictJavaPackagesTestApp.apk";
+
+ private static ImmutableList<String> sBootclasspathJars;
+ private static ImmutableList<String> sSystemserverclasspathJars;
+ private static ImmutableList<String> sSharedLibJars;
+ private static ImmutableList<SharedLibraryInfo> sSharedLibs;
+ private static ImmutableMultimap<String, String> sJarsToClasses;
+
+ private DeviceSdkLevel mDeviceSdkLevel;
+
/**
* This is the list of classes that are currently duplicated and should be addressed.
*
@@ -191,18 +215,604 @@
"Landroid/os/InputEventInjectionResult;",
"Landroid/os/InputEventInjectionSync;",
"Landroid/os/TouchOcclusionMode;",
+ "Landroid/os/ReconcileSdkDataArgs;",
"Lcom/android/internal/util/FrameworkStatsLog;"
);
+ private static final String FEATURE_WEARABLE = "android.hardware.type.watch";
+ private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+ private static final Set<String> WEAR_HIDL_OVERLAP_BURNDOWN_LIST =
+ ImmutableSet.of(
+ "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
+ "Landroid/hidl/base/V1_0/IBase;",
+ "Landroid/hidl/base/V1_0/IBase$Proxy;",
+ "Landroid/hidl/base/V1_0/IBase$Stub;",
+ "Landroid/hidl/base/V1_0/DebugInfo;",
+ "Landroid/hidl/safe_union/V1_0/Monostate;"
+ );
+
+ private static final Set<String> AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST =
+ ImmutableSet.of(
+ "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
+ "Landroid/hidl/base/V1_0/IBase;",
+ "Landroid/hidl/base/V1_0/IBase$Proxy;",
+ "Landroid/hidl/base/V1_0/IBase$Stub;",
+ "Landroid/hidl/base/V1_0/DebugInfo;"
+ );
+
+ /**
+ * TODO(b/199529199): Address these.
+ * List of duplicate classes between bootclasspath and shared libraries.
+ *
+ * <p> DO NOT ADD CLASSES TO THIS LIST!
+ */
+ private static final Set<String> BCP_AND_SHARED_LIB_BURNDOWN_LIST =
+ ImmutableSet.of(
+ "Landroid/hidl/base/V1_0/DebugInfo;",
+ "Landroid/hidl/base/V1_0/IBase;",
+ "Landroid/hidl/manager/V1_0/IServiceManager;",
+ "Landroid/hidl/manager/V1_0/IServiceNotification;",
+ "Landroidx/annotation/Keep;",
+ "Lcom/google/android/embms/nano/EmbmsProtos;",
+ "Lcom/google/protobuf/nano/android/ParcelableExtendableMessageNano;",
+ "Lcom/google/protobuf/nano/android/ParcelableMessageNano;",
+ "Lcom/google/protobuf/nano/android/ParcelableMessageNanoCreator;",
+ "Lcom/google/protobuf/nano/CodedInputByteBufferNano;",
+ "Lcom/google/protobuf/nano/CodedOutputByteBufferNano;",
+ "Lcom/google/protobuf/nano/ExtendableMessageNano;",
+ "Lcom/google/protobuf/nano/Extension;",
+ "Lcom/google/protobuf/nano/FieldArray;",
+ "Lcom/google/protobuf/nano/FieldData;",
+ "Lcom/google/protobuf/nano/InternalNano;",
+ "Lcom/google/protobuf/nano/InvalidProtocolBufferNanoException;",
+ "Lcom/google/protobuf/nano/MapFactories;",
+ "Lcom/google/protobuf/nano/MessageNano;",
+ "Lcom/google/protobuf/nano/MessageNanoPrinter;",
+ "Lcom/google/protobuf/nano/UnknownFieldData;",
+ "Lcom/google/protobuf/nano/WireFormatNano;",
+ "Lcom/qualcomm/qcrilhook/BaseQmiTypes;",
+ "Lcom/qualcomm/qcrilhook/CSignalStrength;",
+ "Lcom/qualcomm/qcrilhook/EmbmsOemHook;",
+ "Lcom/qualcomm/qcrilhook/EmbmsProtoUtils;",
+ "Lcom/qualcomm/qcrilhook/IOemHookCallback;",
+ "Lcom/qualcomm/qcrilhook/IQcRilHook;",
+ "Lcom/qualcomm/qcrilhook/IQcRilHookExt;",
+ "Lcom/qualcomm/qcrilhook/OemHookCallback;",
+ "Lcom/qualcomm/qcrilhook/PresenceMsgBuilder;",
+ "Lcom/qualcomm/qcrilhook/PresenceMsgParser;",
+ "Lcom/qualcomm/qcrilhook/PresenceOemHook;",
+ "Lcom/qualcomm/qcrilhook/PrimitiveParser;",
+ "Lcom/qualcomm/qcrilhook/QcRilHook;",
+ "Lcom/qualcomm/qcrilhook/QcRilHookCallback;",
+ "Lcom/qualcomm/qcrilhook/QcRilHookCallbackExt;",
+ "Lcom/qualcomm/qcrilhook/QcRilHookExt;",
+ "Lcom/qualcomm/qcrilhook/QmiOemHook;",
+ "Lcom/qualcomm/qcrilhook/QmiOemHookConstants;",
+ "Lcom/qualcomm/qcrilhook/QmiPrimitiveTypes;",
+ "Lcom/qualcomm/qcrilhook/TunerOemHook;",
+ "Lcom/qualcomm/qcrilmsgtunnel/IQcrilMsgTunnel;",
+ "Lcom/qualcomm/utils/CommandException;",
+ "Lcom/qualcomm/utils/RILConstants;",
+ "Lorg/codeaurora/telephony/utils/CommandException;",
+ "Lorg/codeaurora/telephony/utils/Log;",
+ "Lorg/codeaurora/telephony/utils/RILConstants;",
+ "Lorg/chromium/net/ApiVersion;",
+ "Lorg/chromium/net/BidirectionalStream;",
+ "Lorg/chromium/net/CallbackException;",
+ "Lorg/chromium/net/CronetEngine;",
+ "Lorg/chromium/net/CronetException;",
+ "Lorg/chromium/net/CronetProvider;",
+ "Lorg/chromium/net/EffectiveConnectionType;",
+ "Lorg/chromium/net/ExperimentalBidirectionalStream;",
+ "Lorg/chromium/net/ExperimentalCronetEngine;",
+ "Lorg/chromium/net/ExperimentalUrlRequest;",
+ "Lorg/chromium/net/ICronetEngineBuilder;",
+ "Lorg/chromium/net/InlineExecutionProhibitedException;",
+ "Lorg/chromium/net/NetworkException;",
+ "Lorg/chromium/net/NetworkQualityRttListener;",
+ "Lorg/chromium/net/NetworkQualityThroughputListener;",
+ "Lorg/chromium/net/QuicException;",
+ "Lorg/chromium/net/RequestFinishedInfo;",
+ "Lorg/chromium/net/RttThroughputValues;",
+ "Lorg/chromium/net/ThreadStatsUid;",
+ "Lorg/chromium/net/UploadDataProvider;",
+ "Lorg/chromium/net/UploadDataProviders;",
+ "Lorg/chromium/net/UploadDataSink;",
+ "Lorg/chromium/net/UrlRequest;",
+ "Lorg/chromium/net/UrlResponseInfo;"
+ );
+ // TODO: b/223837004
+ private static final ImmutableSet<String> BLUETOOTH_APK_IN_APEX_BURNDOWN_LIST =
+ ImmutableSet.of(
+ // Already duplicate in BCP.
+ "Landroid/hidl/base/V1_0/DebugInfo;",
+ "Landroid/hidl/base/V1_0/IBase;",
+ // /apex/com.android.bluetooth/javalib/framework-bluetooth.jar
+ "Lcom/android/bluetooth/x/android/sysprop/AdbProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/ApkVerityProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/BluetoothProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/CarProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/ContactsProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/CryptoProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/DeviceProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/DisplayProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/HdmiProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/HypervisorProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/InputProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/MediaProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/NetworkProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/OtaProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/PowerProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/SetupWizardProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/SocProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/TelephonyProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/TraceProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/VndkProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/VoldProperties;",
+ "Lcom/android/bluetooth/x/android/sysprop/WifiProperties;",
+ "Lcom/android/bluetooth/x/com/android/modules/utils/ISynchronousResultReceiver;",
+ "Lcom/android/bluetooth/x/com/android/modules/utils/SynchronousResultReceiver-IA;",
+ "Lcom/android/bluetooth/x/com/android/modules/utils/SynchronousResultReceiver;",
+ // /system/framework/services.jar
+ "Landroid/net/DataStallReportParcelable;",
+ "Landroid/net/DhcpResultsParcelable;",
+ "Landroid/net/IIpMemoryStore;",
+ "Landroid/net/IIpMemoryStoreCallbacks;",
+ "Landroid/net/INetworkMonitor;",
+ "Landroid/net/INetworkMonitorCallbacks;",
+ "Landroid/net/INetworkStackConnector;",
+ "Landroid/net/INetworkStackStatusCallback;",
+ "Landroid/net/InformationElementParcelable;",
+ "Landroid/net/InitialConfigurationParcelable;",
+ "Landroid/net/IpMemoryStoreClient;",
+ "Landroid/net/Layer2InformationParcelable;",
+ "Landroid/net/Layer2PacketParcelable;",
+ "Landroid/net/NattKeepalivePacketDataParcelable;",
+ "Landroid/net/NetworkFactory;",
+ "Landroid/net/NetworkFactoryShim;",
+ "Landroid/net/NetworkMonitorManager;",
+ "Landroid/net/NetworkTestResultParcelable;",
+ "Landroid/net/PrivateDnsConfigParcel;",
+ "Landroid/net/ProvisioningConfigurationParcelable;",
+ "Landroid/net/ScanResultInfoParcelable;",
+ "Landroid/net/TcpKeepalivePacketDataParcelable;",
+ "Landroid/net/dhcp/DhcpLeaseParcelable;",
+ "Landroid/net/dhcp/DhcpServingParamsParcel;",
+ "Landroid/net/dhcp/IDhcpEventCallbacks;",
+ "Landroid/net/dhcp/IDhcpServer;",
+ "Landroid/net/dhcp/IDhcpServerCallbacks;",
+ "Landroid/net/ip/IIpClient;",
+ "Landroid/net/ip/IIpClientCallbacks;",
+ "Landroid/net/ip/IpClientCallbacks;",
+ "Landroid/net/ip/IpClientManager;",
+ "Landroid/net/ip/IpClientUtil;",
+ "Landroid/net/ipmemorystore/Blob;",
+ "Landroid/net/ipmemorystore/IOnBlobRetrievedListener;",
+ "Landroid/net/ipmemorystore/IOnL2KeyResponseListener;",
+ "Landroid/net/ipmemorystore/IOnNetworkAttributesRetrievedListener;",
+ "Landroid/net/ipmemorystore/IOnSameL3NetworkResponseListener;",
+ "Landroid/net/ipmemorystore/IOnStatusAndCountListener;",
+ "Landroid/net/ipmemorystore/IOnStatusListener;",
+ "Landroid/net/ipmemorystore/NetworkAttributes;",
+ "Landroid/net/ipmemorystore/NetworkAttributesParcelable;",
+ "Landroid/net/ipmemorystore/OnBlobRetrievedListener;",
+ "Landroid/net/ipmemorystore/OnDeleteStatusListener;",
+ "Landroid/net/ipmemorystore/OnL2KeyResponseListener;",
+ "Landroid/net/ipmemorystore/OnNetworkAttributesRetrievedListener;",
+ "Landroid/net/ipmemorystore/OnSameL3NetworkResponseListener;",
+ "Landroid/net/ipmemorystore/OnStatusListener;",
+ "Landroid/net/ipmemorystore/SameL3NetworkResponse;",
+ "Landroid/net/ipmemorystore/SameL3NetworkResponseParcelable;",
+ "Landroid/net/ipmemorystore/Status;",
+ "Landroid/net/ipmemorystore/StatusParcelable;",
+ "Landroid/net/networkstack/NetworkStackClientBase;",
+ "Landroid/net/networkstack/aidl/NetworkMonitorParameters;",
+ "Landroid/net/networkstack/aidl/dhcp/DhcpOption;",
+ "Landroid/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable;",
+ "Landroid/net/networkstack/aidl/ip/ReachabilityLossReason;",
+ "Landroid/net/networkstack/aidl/quirks/IPv6ProvisioningLossQuirk;",
+ "Landroid/net/networkstack/aidl/quirks/IPv6ProvisioningLossQuirkParcelable;",
+ "Landroid/net/shared/InitialConfiguration;",
+ "Landroid/net/shared/IpConfigurationParcelableUtil;",
+ "Landroid/net/shared/Layer2Information;",
+ "Landroid/net/shared/ParcelableUtil;",
+ "Landroid/net/shared/PrivateDnsConfig;",
+ "Landroid/net/shared/ProvisioningConfiguration;",
+ "Landroid/net/util/KeepalivePacketDataUtil;",
+ "Landroid/net/IpMemoryStore;",
+ "Landroid/net/NetworkFactoryLegacyImpl;",
+ "Landroid/net/networkstack/ModuleNetworkStackClient;",
+ "Landroid/net/NetworkFactoryImpl;",
+ // /system/framework/framework.jar
+ "Landroid/bluetooth/BluetoothProtoEnums;",
+ "Landroid/bluetooth/a2dp/BluetoothA2dpProtoEnums;",
+ "Landroid/bluetooth/hci/BluetoothHciProtoEnums;",
+ "Landroid/bluetooth/hfp/BluetoothHfpProtoEnums;",
+ "Landroid/bluetooth/smp/BluetoothSmpProtoEnums;",
+ "Landroid/hardware/radio/V1_0/ActivityStatsInfo;",
+ "Landroid/hardware/radio/V1_0/ApnAuthType;",
+ "Landroid/hardware/radio/V1_0/ApnTypes;",
+ "Landroid/hardware/radio/V1_0/AppState;",
+ "Landroid/hardware/radio/V1_0/AppStatus;",
+ "Landroid/hardware/radio/V1_0/AppType;",
+ "Landroid/hardware/radio/V1_0/Call;",
+ "Landroid/hardware/radio/V1_0/CallForwardInfo;",
+ "Landroid/hardware/radio/V1_0/CallForwardInfoStatus;",
+ "Landroid/hardware/radio/V1_0/CallPresentation;",
+ "Landroid/hardware/radio/V1_0/CallState;",
+ "Landroid/hardware/radio/V1_0/CardState;",
+ "Landroid/hardware/radio/V1_0/CardStatus;",
+ "Landroid/hardware/radio/V1_0/Carrier;",
+ "Landroid/hardware/radio/V1_0/CarrierMatchType;",
+ "Landroid/hardware/radio/V1_0/CarrierRestrictions;",
+ "Landroid/hardware/radio/V1_0/CdmaBroadcastSmsConfigInfo;",
+ "Landroid/hardware/radio/V1_0/CdmaCallWaiting;",
+ "Landroid/hardware/radio/V1_0/CdmaCallWaitingNumberPlan;",
+ "Landroid/hardware/radio/V1_0/CdmaCallWaitingNumberPresentation;",
+ "Landroid/hardware/radio/V1_0/CdmaCallWaitingNumberType;",
+ "Landroid/hardware/radio/V1_0/CdmaDisplayInfoRecord;",
+ "Landroid/hardware/radio/V1_0/CdmaInfoRecName;",
+ "Landroid/hardware/radio/V1_0/CdmaInformationRecord;",
+ "Landroid/hardware/radio/V1_0/CdmaInformationRecords;",
+ "Landroid/hardware/radio/V1_0/CdmaLineControlInfoRecord;",
+ "Landroid/hardware/radio/V1_0/CdmaNumberInfoRecord;",
+ "Landroid/hardware/radio/V1_0/CdmaOtaProvisionStatus;",
+ "Landroid/hardware/radio/V1_0/CdmaRedirectingNumberInfoRecord;",
+ "Landroid/hardware/radio/V1_0/CdmaRedirectingReason;",
+ "Landroid/hardware/radio/V1_0/CdmaRoamingType;",
+ "Landroid/hardware/radio/V1_0/CdmaSignalInfoRecord;",
+ "Landroid/hardware/radio/V1_0/CdmaSignalStrength;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsAck;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsAddress;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsDigitMode;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsErrorClass;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsMessage;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsNumberMode;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsNumberPlan;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsNumberType;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsSubaddress;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsSubaddressType;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsWriteArgs;",
+ "Landroid/hardware/radio/V1_0/CdmaSmsWriteArgsStatus;",
+ "Landroid/hardware/radio/V1_0/CdmaSubscriptionSource;",
+ "Landroid/hardware/radio/V1_0/CdmaT53AudioControlInfoRecord;",
+ "Landroid/hardware/radio/V1_0/CdmaT53ClirInfoRecord;",
+ "Landroid/hardware/radio/V1_0/CellIdentity;",
+ "Landroid/hardware/radio/V1_0/CellIdentityCdma;",
+ "Landroid/hardware/radio/V1_0/CellIdentityGsm;",
+ "Landroid/hardware/radio/V1_0/CellIdentityLte;",
+ "Landroid/hardware/radio/V1_0/CellIdentityTdscdma;",
+ "Landroid/hardware/radio/V1_0/CellIdentityWcdma;",
+ "Landroid/hardware/radio/V1_0/CellInfo;",
+ "Landroid/hardware/radio/V1_0/CellInfoCdma;",
+ "Landroid/hardware/radio/V1_0/CellInfoGsm;",
+ "Landroid/hardware/radio/V1_0/CellInfoLte;",
+ "Landroid/hardware/radio/V1_0/CellInfoTdscdma;",
+ "Landroid/hardware/radio/V1_0/CellInfoType;",
+ "Landroid/hardware/radio/V1_0/CellInfoWcdma;",
+ "Landroid/hardware/radio/V1_0/CfData;",
+ "Landroid/hardware/radio/V1_0/ClipStatus;",
+ "Landroid/hardware/radio/V1_0/Clir;",
+ "Landroid/hardware/radio/V1_0/DataCallFailCause;",
+ "Landroid/hardware/radio/V1_0/DataProfileId;",
+ "Landroid/hardware/radio/V1_0/DataProfileInfo;",
+ "Landroid/hardware/radio/V1_0/DataProfileInfoType;",
+ "Landroid/hardware/radio/V1_0/DataRegStateResult;",
+ "Landroid/hardware/radio/V1_0/DeviceStateType;",
+ "Landroid/hardware/radio/V1_0/Dial;",
+ "Landroid/hardware/radio/V1_0/EvdoSignalStrength;",
+ "Landroid/hardware/radio/V1_0/GsmBroadcastSmsConfigInfo;",
+ "Landroid/hardware/radio/V1_0/GsmSignalStrength;",
+ "Landroid/hardware/radio/V1_0/GsmSmsMessage;",
+ "Landroid/hardware/radio/V1_0/HardwareConfig;",
+ "Landroid/hardware/radio/V1_0/HardwareConfigModem;",
+ "Landroid/hardware/radio/V1_0/HardwareConfigSim;",
+ "Landroid/hardware/radio/V1_0/HardwareConfigState;",
+ "Landroid/hardware/radio/V1_0/HardwareConfigType;",
+ "Landroid/hardware/radio/V1_0/IccIo;",
+ "Landroid/hardware/radio/V1_0/IccIoResult;",
+ "Landroid/hardware/radio/V1_0/ImsSmsMessage;",
+ "Landroid/hardware/radio/V1_0/IndicationFilter;",
+ "Landroid/hardware/radio/V1_0/LastCallFailCause;",
+ "Landroid/hardware/radio/V1_0/LastCallFailCauseInfo;",
+ "Landroid/hardware/radio/V1_0/LceDataInfo;",
+ "Landroid/hardware/radio/V1_0/LceStatus;",
+ "Landroid/hardware/radio/V1_0/LceStatusInfo;",
+ "Landroid/hardware/radio/V1_0/LteSignalStrength;",
+ "Landroid/hardware/radio/V1_0/MvnoType;",
+ "Landroid/hardware/radio/V1_0/NeighboringCell;",
+ "Landroid/hardware/radio/V1_0/NvItem;",
+ "Landroid/hardware/radio/V1_0/NvWriteItem;",
+ "Landroid/hardware/radio/V1_0/OperatorInfo;",
+ "Landroid/hardware/radio/V1_0/OperatorStatus;",
+ "Landroid/hardware/radio/V1_0/P2Constant;",
+ "Landroid/hardware/radio/V1_0/PcoDataInfo;",
+ "Landroid/hardware/radio/V1_0/PersoSubstate;",
+ "Landroid/hardware/radio/V1_0/PhoneRestrictedState;",
+ "Landroid/hardware/radio/V1_0/PinState;",
+ "Landroid/hardware/radio/V1_0/PreferredNetworkType;",
+ "Landroid/hardware/radio/V1_0/RadioAccessFamily;",
+ "Landroid/hardware/radio/V1_0/RadioBandMode;",
+ "Landroid/hardware/radio/V1_0/RadioCapability;",
+ "Landroid/hardware/radio/V1_0/RadioCapabilityPhase;",
+ "Landroid/hardware/radio/V1_0/RadioCapabilityStatus;",
+ "Landroid/hardware/radio/V1_0/RadioCdmaSmsConst;",
+ "Landroid/hardware/radio/V1_0/RadioConst;",
+ "Landroid/hardware/radio/V1_0/RadioError;",
+ "Landroid/hardware/radio/V1_0/RadioIndicationType;",
+ "Landroid/hardware/radio/V1_0/RadioResponseInfo;",
+ "Landroid/hardware/radio/V1_0/RadioResponseType;",
+ "Landroid/hardware/radio/V1_0/RadioState;",
+ "Landroid/hardware/radio/V1_0/RadioTechnology;",
+ "Landroid/hardware/radio/V1_0/RadioTechnologyFamily;",
+ "Landroid/hardware/radio/V1_0/RegState;",
+ "Landroid/hardware/radio/V1_0/ResetNvType;",
+ "Landroid/hardware/radio/V1_0/RestrictedState;",
+ "Landroid/hardware/radio/V1_0/SapApduType;",
+ "Landroid/hardware/radio/V1_0/SapConnectRsp;",
+ "Landroid/hardware/radio/V1_0/SapDisconnectType;",
+ "Landroid/hardware/radio/V1_0/SapResultCode;",
+ "Landroid/hardware/radio/V1_0/SapStatus;",
+ "Landroid/hardware/radio/V1_0/SapTransferProtocol;",
+ "Landroid/hardware/radio/V1_0/SelectUiccSub;",
+ "Landroid/hardware/radio/V1_0/SendSmsResult;",
+ "Landroid/hardware/radio/V1_0/SetupDataCallResult;",
+ "Landroid/hardware/radio/V1_0/SignalStrength;",
+ "Landroid/hardware/radio/V1_0/SimApdu;",
+ "Landroid/hardware/radio/V1_0/SimRefreshResult;",
+ "Landroid/hardware/radio/V1_0/SimRefreshType;",
+ "Landroid/hardware/radio/V1_0/SmsAcknowledgeFailCause;",
+ "Landroid/hardware/radio/V1_0/SmsWriteArgs;",
+ "Landroid/hardware/radio/V1_0/SmsWriteArgsStatus;",
+ "Landroid/hardware/radio/V1_0/SrvccState;",
+ "Landroid/hardware/radio/V1_0/SsInfoData;",
+ "Landroid/hardware/radio/V1_0/SsRequestType;",
+ "Landroid/hardware/radio/V1_0/SsServiceType;",
+ "Landroid/hardware/radio/V1_0/SsTeleserviceType;",
+ "Landroid/hardware/radio/V1_0/StkCcUnsolSsResult;",
+ "Landroid/hardware/radio/V1_0/SubscriptionType;",
+ "Landroid/hardware/radio/V1_0/SuppServiceClass;",
+ "Landroid/hardware/radio/V1_0/SuppSvcNotification;",
+ "Landroid/hardware/radio/V1_0/TdScdmaSignalStrength;",
+ "Landroid/hardware/radio/V1_0/TimeStampType;",
+ "Landroid/hardware/radio/V1_0/TtyMode;",
+ "Landroid/hardware/radio/V1_0/UiccSubActStatus;",
+ "Landroid/hardware/radio/V1_0/UssdModeType;",
+ "Landroid/hardware/radio/V1_0/UusDcs;",
+ "Landroid/hardware/radio/V1_0/UusInfo;",
+ "Landroid/hardware/radio/V1_0/UusType;",
+ "Landroid/hardware/radio/V1_0/VoiceRegStateResult;",
+ "Landroid/hardware/radio/V1_0/WcdmaSignalStrength;",
+ "Landroid/hardware/radio/V1_0/IRadio;",
+ "Landroid/hardware/radio/V1_0/IRadioIndication;",
+ "Landroid/hardware/radio/V1_0/IRadioResponse;",
+ "Landroid/hardware/radio/V1_0/ISap;",
+ "Landroid/hardware/radio/V1_0/ISapCallback;",
+ "Lcom/android/internal/util/IState;",
+ "Lcom/android/internal/util/StateMachine;",
+ "Lcom/google/android/mms/ContentType;",
+ "Lcom/google/android/mms/MmsException;",
+ "Lcom/google/android/mms/pdu/Base64;",
+ "Lcom/google/android/mms/pdu/CharacterSets;",
+ "Lcom/google/android/mms/pdu/EncodedStringValue;",
+ "Lcom/google/android/mms/pdu/GenericPdu;",
+ "Lcom/google/android/mms/pdu/PduBody;",
+ "Lcom/google/android/mms/pdu/PduComposer;",
+ "Lcom/google/android/mms/pdu/PduContentTypes;",
+ "Lcom/google/android/mms/pdu/PduHeaders;",
+ "Lcom/google/android/mms/pdu/PduParser;",
+ "Lcom/google/android/mms/pdu/PduPart;",
+ "Lcom/google/android/mms/pdu/PduPersister;",
+ "Lcom/google/android/mms/pdu/QuotedPrintable;",
+ "Lcom/google/android/mms/util/AbstractCache;",
+ "Lcom/google/android/mms/util/DownloadDrmHelper;",
+ "Lcom/google/android/mms/util/DrmConvertSession;",
+ "Lcom/google/android/mms/util/PduCacheEntry;",
+ "Lcom/google/android/mms/util/SqliteWrapper;",
+ "Lcom/android/internal/util/State;",
+ "Lcom/google/android/mms/InvalidHeaderValueException;",
+ "Lcom/google/android/mms/pdu/AcknowledgeInd;",
+ "Lcom/google/android/mms/pdu/DeliveryInd;",
+ "Lcom/google/android/mms/pdu/MultimediaMessagePdu;",
+ "Lcom/google/android/mms/pdu/NotificationInd;",
+ "Lcom/google/android/mms/pdu/NotifyRespInd;",
+ "Lcom/google/android/mms/pdu/ReadOrigInd;",
+ "Lcom/google/android/mms/pdu/ReadRecInd;",
+ "Lcom/google/android/mms/pdu/SendConf;",
+ "Lcom/google/android/mms/util/PduCache;",
+ "Lcom/google/android/mms/pdu/RetrieveConf;",
+ "Lcom/google/android/mms/pdu/SendReq;"
+ );
+ private static final ImmutableSet<String> PERMISSION_CONTROLLER_APK_IN_APEX_BURNDOWN_LIST =
+ ImmutableSet.of(
+ "Lcom/android/modules/utils/build/SdkLevel;",
+ "Lcom/android/settingslib/RestrictedLockUtils;"
+ );
+ // TODO: b/223837599
+ private static final ImmutableSet<String> TETHERING_APK_IN_APEX_BURNDOWN_LIST =
+ ImmutableSet.of(
+ "Landroid/hidl/base/V1_0/DebugInfo;",
+ // /system/framework/services.jar
+ "Landroid/net/DataStallReportParcelable;",
+ "Landroid/net/DhcpResultsParcelable;",
+ "Landroid/net/INetd;",
+ "Landroid/net/INetdUnsolicitedEventListener;",
+ "Landroid/net/INetworkStackConnector;",
+ "Landroid/net/INetworkStackStatusCallback;",
+ "Landroid/net/InformationElementParcelable;",
+ "Landroid/net/InitialConfigurationParcelable;",
+ "Landroid/net/InterfaceConfigurationParcel;",
+ "Landroid/net/Layer2InformationParcelable;",
+ "Landroid/net/Layer2PacketParcelable;",
+ "Landroid/net/MarkMaskParcel;",
+ "Landroid/net/NativeNetworkConfig;",
+ "Landroid/net/NattKeepalivePacketDataParcelable;",
+ "Landroid/net/NetworkTestResultParcelable;",
+ "Landroid/net/PrivateDnsConfigParcel;",
+ "Landroid/net/ProvisioningConfigurationParcelable;",
+ "Landroid/net/RouteInfoParcel;",
+ "Landroid/net/ScanResultInfoParcelable;",
+ "Landroid/net/TcpKeepalivePacketDataParcelable;",
+ "Landroid/net/TetherConfigParcel;",
+ "Landroid/net/TetherOffloadRuleParcel;",
+ "Landroid/net/TetherStatsParcel;",
+ "Landroid/net/UidRangeParcel;",
+ "Landroid/net/dhcp/DhcpLeaseParcelable;",
+ "Landroid/net/dhcp/DhcpServingParamsParcel;",
+ "Landroid/net/dhcp/IDhcpEventCallbacks;",
+ "Landroid/net/dhcp/IDhcpServer;",
+ "Landroid/net/dhcp/IDhcpServerCallbacks;",
+ "Landroid/net/ipmemorystore/Blob;",
+ "Landroid/net/ipmemorystore/NetworkAttributesParcelable;",
+ "Landroid/net/ipmemorystore/SameL3NetworkResponseParcelable;",
+ "Landroid/net/ipmemorystore/StatusParcelable;",
+ "Landroid/net/netd/aidl/NativeUidRangeConfig;",
+ "Landroid/net/networkstack/aidl/NetworkMonitorParameters;",
+ "Landroid/net/networkstack/aidl/dhcp/DhcpOption;",
+ "Landroid/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable;",
+ "Landroid/net/networkstack/aidl/quirks/IPv6ProvisioningLossQuirkParcelable;",
+ "Landroid/net/shared/NetdUtils;",
+ "Landroid/net/util/NetworkConstants;",
+ "Landroid/net/util/SharedLog;",
+ "Lcom/android/modules/utils/build/SdkLevel;",
+ ///system/framework/framework.jar
+ "Landroid/util/IndentingPrintWriter;",
+ "Lcom/android/internal/annotations/Keep;"
+ );
+ // TODO: b/223836161
+ private static final ImmutableSet<String> EXTSERVICES_APK_IN_APEX_BURNDOWN_LIST =
+ ImmutableSet.of(
+ ///system/framework/framework.jar
+ "Landroid/view/displayhash/DisplayHashResultCallback;",
+ "Landroid/view/displayhash/DisplayHash;",
+ "Landroid/view/displayhash/VerifiedDisplayHash;"
+ );
+ // TODO: b/223836163
+ private static final ImmutableSet<String> ODA_APK_IN_APEX_BURNDOWN_LIST =
+ ImmutableSet.of(
+ // /apex/com.android.ondevicepersonalization/javalib/framework-ondevicepersonalization.jar
+ "Landroid/ondevicepersonalization/aidl/IInitOnDevicePersonalizationCallback;",
+ "Landroid/ondevicepersonalization/aidl/IOnDevicePersonalizationManagerService;"
+ );
+ // TODO: b/223837016
+ private static final ImmutableSet<String> MEDIAPROVIDER_APK_IN_APEX_BURNDOWN_LIST =
+ ImmutableSet.of(
+ // /system/framework/services.jar
+ "Lcom/android/modules/utils/build/SdkLevel;"
+ );
+ // TODO: b/223837017
+ private static final ImmutableSet<String> CELLBROADCAST_APK_IN_APEX_BURNDOWN_LIST =
+ ImmutableSet.of(
+ // /system/framework/telephony-common.jar
+ "Lcom/android/cellbroadcastservice/CellBroadcastStatsLog;",
+ // /system/framework/framework.jar
+ "Lcom/android/internal/util/IState;",
+ "Lcom/android/internal/util/StateMachine;",
+ "Lcom/android/internal/util/State;"
+ );
+ private static final ImmutableMap<String, ImmutableSet<String>> FULL_APK_IN_APEX_BURNDOWN =
+ new ImmutableMap.Builder<String, ImmutableSet<String>>()
+ .put("/apex/com.android.bluetooth/app/Bluetooth/Bluetooth.apk",
+ BLUETOOTH_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.bluetooth/app/BluetoothGoogle/BluetoothGoogle.apk",
+ BLUETOOTH_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.permission/priv-app/PermissionController/PermissionController.apk",
+ PERMISSION_CONTROLLER_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.permission/priv-app/GooglePermissionController/GooglePermissionController.apk",
+ PERMISSION_CONTROLLER_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.tethering/priv-app/InProcessTethering/InProcessTethering.apk",
+ TETHERING_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.tethering/priv-app/TetheringNextGoogle/TetheringNextGoogle.apk",
+ TETHERING_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.tethering/priv-app/TetheringGoogle/TetheringGoogle.apk",
+ TETHERING_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.tethering/priv-app/TetheringNext/TetheringNext.apk",
+ TETHERING_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.tethering/priv-app/Tethering/Tethering.apk",
+ TETHERING_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.extservices/priv-app/GoogleExtServices/GoogleExtServices.apk",
+ EXTSERVICES_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.extservices/priv-app/ExtServices/ExtServices.apk",
+ EXTSERVICES_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.ondevicepersonalization/app/OnDevicePersonalizationGoogle/OnDevicePersonalizationGoogle.apk",
+ ODA_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.ondevicepersonalization/app/OnDevicePersonalization/OnDevicePersonalization.apk",
+ ODA_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.mediaprovider/priv-app/MediaProviderGoogle/MediaProviderGoogle.apk",
+ MEDIAPROVIDER_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.mediaprovider/priv-app/MediaProvider/MediaProvider.apk",
+ MEDIAPROVIDER_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.cellbroadcast/priv-app/GoogleCellBroadcastServiceModule/GoogleCellBroadcastServiceModule.apk",
+ CELLBROADCAST_APK_IN_APEX_BURNDOWN_LIST)
+ .put("/apex/com.android.cellbroadcast/priv-app/CellBroadcastServiceModule/CellBroadcastServiceModule.apk",
+ CELLBROADCAST_APK_IN_APEX_BURNDOWN_LIST)
+ .build();
+ /**
+ * Fetch all jar files in BCP, SSCP and shared libs and extract all the classes.
+ *
+ * <p>This method cannot be static, as there are no static equivalents for {@link #getDevice()}
+ * and {@link #getBuild()}.
+ */
+ @BeforeClassWithInfo
+ public static void setupOnce(TestInformation testInfo) throws Exception {
+ if (testInfo.getDevice() == null || testInfo.getBuildInfo() == null) {
+ throw new RuntimeException("No device and/or build type specified!");
+ }
+ DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(testInfo.getDevice());
+
+ sBootclasspathJars = Classpaths.getJarsOnClasspath(testInfo.getDevice(), BOOTCLASSPATH);
+ sSystemserverclasspathJars =
+ Classpaths.getJarsOnClasspath(testInfo.getDevice(), SYSTEMSERVERCLASSPATH);
+ sSharedLibs = deviceSdkLevel.isDeviceAtLeastS()
+ ? Classpaths.getSharedLibraryInfos(testInfo.getDevice(), testInfo.getBuildInfo())
+ : ImmutableList.of();
+ sSharedLibJars = sSharedLibs.stream()
+ .map(sharedLibraryInfo -> sharedLibraryInfo.paths)
+ .flatMap(ImmutableCollection::stream)
+ .filter(file -> doesFileExist(file, testInfo.getDevice()))
+ // GmsCore should not contribute to *classpath.
+ .filter(file -> !file.contains("GmsCore"))
+ .filter(file -> !file.contains("com.google.android.gms"))
+ .collect(ImmutableList.toImmutableList());
+
+ final ImmutableSetMultimap.Builder<String, String> jarsToClasses =
+ ImmutableSetMultimap.builder();
+ Stream.of(sBootclasspathJars.stream(),
+ sSystemserverclasspathJars.stream(),
+ sSharedLibJars.stream())
+ .reduce(Stream::concat).orElseGet(Stream::empty)
+ .parallel()
+ .forEach(jar -> {
+ try {
+ ImmutableSet<String> classes =
+ Classpaths.getClassDefsFromJar(testInfo.getDevice(), jar).stream()
+ .map(ClassDef::getType)
+ // Inner classes always go with their parent.
+ .filter(className -> !className.contains("$"))
+ .collect(ImmutableSet.toImmutableSet());
+ synchronized (jarsToClasses) {
+ jarsToClasses.putAll(jar, classes);
+ }
+ } catch (DeviceNotAvailableException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ sJarsToClasses = jarsToClasses.build();
+ }
+
+ @Before
+ public void setup() {
+ mDeviceSdkLevel = new DeviceSdkLevel(getDevice());
+ }
+
/**
* Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH.
*/
@Test
public void testBootclasspath_nonDuplicateClasses() throws Exception {
- assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
- ImmutableList<String> jars =
- Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
- assertThat(getDuplicateClasses(jars)).isEmpty();
+ assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
+ assertThat(getDuplicateClasses(sBootclasspathJars)).isEmpty();
}
/**
@@ -210,10 +820,20 @@
*/
@Test
public void testSystemServerClasspath_nonDuplicateClasses() throws Exception {
- assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
- ImmutableList<String> jars =
- Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
- assertThat(getDuplicateClasses(jars)).isEmpty();
+ assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
+ ImmutableSet<String> overlapBurndownList;
+ if (hasFeature(FEATURE_AUTOMOTIVE)) {
+ overlapBurndownList = ImmutableSet.copyOf(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST);
+ } else if (hasFeature(FEATURE_WEARABLE)) {
+ overlapBurndownList = ImmutableSet.copyOf(WEAR_HIDL_OVERLAP_BURNDOWN_LIST);
+ } else {
+ overlapBurndownList = ImmutableSet.of();
+ }
+ Multimap<String, String> duplicates = getDuplicateClasses(sSystemserverclasspathJars);
+ Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
+ duplicate -> !overlapBurndownList.contains(duplicate));
+
+ assertThat(filtered).isEmpty();
}
/**
@@ -222,14 +842,25 @@
*/
@Test
public void testBootClasspathAndSystemServerClasspath_nonDuplicateClasses() throws Exception {
- assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
+ assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
ImmutableList.Builder<String> jars = ImmutableList.builder();
- jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH));
- jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH));
-
+ jars.addAll(sBootclasspathJars);
+ jars.addAll(sSystemserverclasspathJars);
+ ImmutableSet<String> overlapBurndownList;
+ if (hasFeature(FEATURE_AUTOMOTIVE)) {
+ overlapBurndownList = ImmutableSet.<String>builder()
+ .addAll(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST)
+ .addAll(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST).build();
+ } else if (hasFeature(FEATURE_WEARABLE)) {
+ overlapBurndownList = ImmutableSet.<String>builder()
+ .addAll(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST)
+ .addAll(WEAR_HIDL_OVERLAP_BURNDOWN_LIST).build();
+ } else {
+ overlapBurndownList = ImmutableSet.copyOf(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST);
+ }
Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
- duplicate -> !BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST.contains(duplicate));
+ duplicate -> !overlapBurndownList.contains(duplicate));
assertThat(filtered).isEmpty();
}
@@ -239,13 +870,9 @@
*/
@Test
public void testBootClasspath_nonDuplicateApexJarClasses() throws Exception {
- ImmutableList<String> jars =
- Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
-
- Multimap<String, String> duplicates = getDuplicateClasses(jars);
+ Multimap<String, String> duplicates = getDuplicateClasses(sBootclasspathJars);
Multimap<String, String> filtered =
Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/"));
-
assertThat(filtered).isEmpty();
}
@@ -254,10 +881,7 @@
*/
@Test
public void testSystemServerClasspath_nonDuplicateApexJarClasses() throws Exception {
- ImmutableList<String> jars =
- Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
-
- Multimap<String, String> duplicates = getDuplicateClasses(jars);
+ Multimap<String, String> duplicates = getDuplicateClasses(sSystemserverclasspathJars);
Multimap<String, String> filtered =
Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/"));
@@ -272,8 +896,8 @@
public void testBootClasspathAndSystemServerClasspath_nonApexDuplicateClasses()
throws Exception {
ImmutableList.Builder<String> jars = ImmutableList.builder();
- jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH));
- jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH));
+ jars.addAll(sBootclasspathJars);
+ jars.addAll(sSystemserverclasspathJars);
Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
@@ -284,33 +908,164 @@
}
/**
+ * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH and
+ * shared library jars.
+ */
+ @Test
+ public void testBootClasspathAndSharedLibs_nonDuplicateClasses() throws Exception {
+ assumeTrue(mDeviceSdkLevel.isDeviceAtLeastS());
+ final ImmutableList.Builder<String> jars = ImmutableList.builder();
+ jars.addAll(sBootclasspathJars);
+ jars.addAll(sSharedLibJars);
+ final Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
+ final Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
+ dupeClass -> {
+ try {
+ final Collection<String> dupeJars = duplicates.get(dupeClass);
+ // Duplicate is already known.
+ if (BCP_AND_SHARED_LIB_BURNDOWN_LIST.contains(dupeClass)) {
+ return false;
+ }
+ // Duplicate is only between different versions of the same shared library.
+ if (isSameLibrary(dupeJars)) {
+ return false;
+ }
+ // Pre-T, the Android test mock library included some platform classes.
+ if (!mDeviceSdkLevel.isDeviceAtLeastT()
+ && dupeJars.contains(ANDROID_TEST_MOCK_JAR)) {
+ return false;
+ }
+ // Different versions of the same library may have different names, and
+ // there's
+ // no reliable way to dedupe them. Ignore duplicates if they do not
+ // include apex jars.
+ if (dupeJars.stream().noneMatch(lib -> lib.startsWith("/apex/"))) {
+ return false;
+ }
+ } catch (DeviceNotAvailableException e) {
+ throw new RuntimeException(e);
+ }
+ return true;
+ });
+ assertThat(filtered).isEmpty();
+ }
+
+ /**
+ * Ensure that no apk-in-apex bundles classes that could be eclipsed by jars in
+ * BOOTCLASSPATH, SYSTEMSERVERCLASSPATH.
+ */
+ @Test
+ public void testApkInApex_nonClasspathClasses() throws Exception {
+ HashMultimap<String, Multimap<String, String>> perApkClasspathDuplicates =
+ HashMultimap.create();
+ Arrays.stream(collectApkInApexPaths())
+ .parallel()
+ .forEach(apk -> {
+ try {
+ final ImmutableSet<String> apkClasses =
+ Classpaths.getClassDefsFromJar(getDevice(), apk).stream()
+ .map(ClassDef::getType)
+ .collect(ImmutableSet.toImmutableSet());
+ // b/226559955: The directory paths containing APKs contain the build ID,
+ // so strip out the @BUILD_ID portion.
+ // e.g. /apex/com.android.bluetooth/app/Bluetooth@SC-DEV/Bluetooth.apk ->
+ // /apex/com.android.bluetooth/app/Bluetooth/Bluetooth.apk
+ apk = apk.replaceFirst("@[^/]*", "");
+ final ImmutableSet<String> burndownClasses =
+ FULL_APK_IN_APEX_BURNDOWN.getOrDefault(apk, ImmutableSet.of());
+ final Multimap<String, String> duplicates =
+ Multimaps.filterValues(sJarsToClasses, apkClasses::contains);
+ final Multimap<String, String> filteredDuplicates =
+ Multimaps.filterValues(duplicates,
+ className -> !burndownClasses.contains(className)
+ // TODO: b/225341497
+ && !className.equals("Landroidx/annotation/Keep;"));
+ if (!filteredDuplicates.isEmpty()) {
+ synchronized (perApkClasspathDuplicates) {
+ perApkClasspathDuplicates.put(apk, filteredDuplicates);
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ assertThat(perApkClasspathDuplicates).isEmpty();
+ }
+
+ private String[] collectApkInApexPaths() {
+ try {
+ final CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+ final String installError = getDevice().installPackage(
+ buildHelper.getTestFile(TEST_HELPER_APK), false);
+ assertWithMessage("Failed to install %s due to: %s", TEST_HELPER_APK, installError)
+ .that(installError).isNull();
+ runDeviceTests(new DeviceTestRunOptions(TEST_HELPER_PACKAGE)
+ .setDevice(getDevice())
+ .setTestClassName(TEST_HELPER_PACKAGE + ".ApexDeviceTest")
+ .setTestMethodName("testCollectApkInApexPaths"));
+ final String remoteFile = "/sdcard/apk-in-apex-paths.txt";
+ String content;
+ try {
+ content = getDevice().pullFileContents(remoteFile);
+ } finally {
+ getDevice().deleteFile(remoteFile);
+ }
+ return content.split("\n");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ getDevice().uninstallPackage(TEST_HELPER_PACKAGE);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
* Gets the duplicate classes within a list of jar files.
*
* @param jars a list of jar files.
* @return a multimap with the class name as a key and the jar files as a value.
*/
- private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars)
- throws Exception {
- final Multimap<String, String> allClasses = HashMultimap.create();
- for (String jar : jars) {
- ImmutableSet<ClassDef> classes = Classpaths.getClassDefsFromJar(getDevice(), jar);
- for (ClassDef classDef : classes) {
- // No need to worry about inner classes, as they always go with their parent.
- if (!classDef.getType().contains("$")) {
- allClasses.put(classDef.getType(), jar);
- }
- }
- }
+ private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars) {
+ final HashMultimap<String, String> allClasses = HashMultimap.create();
+ Multimaps.invertFrom(Multimaps.filterKeys(sJarsToClasses, jars::contains), allClasses);
+ return Multimaps.filterKeys(allClasses, key -> allClasses.get(key).size() > 1);
+ }
- final Multimap<String, String> duplicates = HashMultimap.create();
- for (String clazz : allClasses.keySet()) {
- Collection<String> jarsWithClazz = allClasses.get(clazz);
- if (jarsWithClazz.size() > 1) {
- CLog.i("Class %s is duplicated in %s", clazz, jarsWithClazz);
- duplicates.putAll(clazz, jarsWithClazz);
- }
+ private static boolean doesFileExist(String path, ITestDevice device) {
+ assertThat(path).isNotNull();
+ try {
+ return device.doesFileExist(path);
+ } catch (DeviceNotAvailableException e) {
+ throw new RuntimeException("Could not check whether " + path + " exists on device", e);
}
+ }
- return duplicates;
+ /**
+ * Get the name of a shared library.
+ *
+ * @return the shared library name or the jar's path if it's not a shared library.
+ */
+ private String getSharedLibraryNameOrPath(String jar) {
+ return sSharedLibs.stream()
+ .filter(sharedLib -> sharedLib.paths.contains(jar))
+ .map(sharedLib -> sharedLib.name)
+ .findFirst().orElse(jar);
+ }
+
+ /**
+ * Check whether a list of jars are all different versions of the same library.
+ */
+ private boolean isSameLibrary(Collection<String> jars) {
+ return jars.stream()
+ .map(this::getSharedLibraryNameOrPath)
+ .distinct()
+ .count() == 1;
+ }
+
+ private boolean hasFeature(String featureName) throws DeviceNotAvailableException {
+ return getDevice().executeShellCommand("pm list features").contains(featureName);
}
}
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
index ef54b1a..ed22bab 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
@@ -23,6 +23,7 @@
defaults: ["cts_support_defaults"],
sdk_version: "test_current",
static_libs: [
+ "androidx.appcompat_appcompat",
"androidx.test.rules",
"compatibility-device-util-axt",
"ctstestrunner-axt",
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 1a2a593..29b6783 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -46,6 +46,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.os.BuildCompat;
import com.android.cts.documentclient.MyActivity.Result;
@@ -431,12 +432,16 @@
// save button is disabled for the storage root
assertFalse(findSaveButton().isEnabled());
- // We should always have Android directory available
- findDocument("Android").click();
- mDevice.waitForIdle();
+ // SAF directory access to /Android is blocked in ag/13163842, this change may not be
+ // present on some R devices, so only test it from above S.
+ if (BuildCompat.isAtLeastS()) {
+ // We should always have Android directory available
+ findDocument("Android").click();
+ mDevice.waitForIdle();
- // save button is disabled for Android folder
- assertFalse(findSaveButton().isEnabled());
+ // save button is disabled for Android folder
+ assertFalse(findSaveButton().isEnabled());
+ }
findRoot(getDeviceName()).click();
mDevice.waitForIdle();
@@ -960,6 +965,9 @@
assertEquals(displayName, getColumn(uri, Document.COLUMN_DISPLAY_NAME));
assertEquals(mimeType, getColumn(uri, Document.COLUMN_MIME_TYPE));
+ // Multiple calls to this method too quickly may result in onActivityResult being skipped.
+ mDevice.waitForIdle();
+
return uri;
}
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
index 36ad932..418f1c4 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
@@ -19,6 +19,7 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -447,6 +448,7 @@
assertAccessFileAPISupport(file);
assertReadWriteFileAPISupport(file);
assertRenameFileAPISupport(file);
+ assertRenameAndReplaceFileAPISupport(file, create);
assertDeleteFileAPISupport(file);
}
@@ -471,15 +473,52 @@
public void assertRenameFileAPISupport(File oldFile) throws Exception {
final String oldName = oldFile.getAbsolutePath();
final String extension = oldName.substring(oldName.lastIndexOf('.')).trim();
- // TODO(b/178816495): Changing the extension changes the media-type and hence the media-URI
- // corresponding to the new file is not accessible to the caller. Rename to the same
- // extension so that the test app does not lose access and is able to delete the file.
- final String newName = "cts" + System.nanoTime() + extension;
- final File newFile = Environment.buildPath(Environment.getExternalStorageDirectory(),
- Environment.DIRECTORY_DOWNLOADS, newName);
- assertThat(oldFile.renameTo(newFile)).isTrue();
+ // Rename to same extension so test app does not lose access to file.
+ final String newRelativeName = "cts" + System.nanoTime() + extension;
+ final File newFile = Environment.buildPath(
+ Environment.getExternalStorageDirectory(),
+ Environment.DIRECTORY_DOWNLOADS,
+ newRelativeName);
+ final String newName = newFile.getAbsolutePath();
+ assertWithMessage("Rename from oldName [%s] to newName [%s]", oldName, newName)
+ .that(oldFile.renameTo(newFile))
+ .isTrue();
// Rename back to oldFile for other ops like delete
- assertThat(newFile.renameTo(oldFile)).isTrue();
+ assertWithMessage("Rename back from newName [%s] to oldName [%s]", newName, oldName)
+ .that(newFile.renameTo(oldFile))
+ .isTrue();
+ }
+
+ public void assertRenameAndReplaceFileAPISupport(File oldFile, Callable<Uri> create)
+ throws Exception {
+ final String oldName = oldFile.getAbsolutePath();
+
+ // Create new file to which we do not have any access.
+ final Uri newUri = create.call();
+ assertWithMessage("Check newFile created").that(newUri).isNotNull();
+ File newFile = new File(queryForSingleColumn(newUri, MediaColumns.DATA));
+ final String newName = newFile.getAbsolutePath();
+ clearMediaOwner(newUri, mUserId);
+
+ assertWithMessage(
+ "Rename should fail without newFile grant from oldName [%s] to newName [%s]",
+ oldName, newName)
+ .that(oldFile.renameTo(newFile))
+ .isFalse();
+
+ // Grant access to newFile and rename should succeed.
+ doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(newUri)));
+ assertWithMessage("Rename from oldName [%s] to newName [%s]", oldName, newName)
+ .that(oldFile.renameTo(newFile))
+ .isTrue();
+
+ // We need to request grant on newUri again, since the rename above caused the URI grant
+ // to be revoked.
+ doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(newUri)));
+ // Rename back to oldFile for other ops like delete
+ assertWithMessage("Rename back from newName [%s] to oldName [%s]", newName, oldName)
+ .that(newFile.renameTo(oldFile))
+ .isTrue();
}
private void assertDeleteFileAPISupport(File file) throws Exception {
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index fbfd706..808a00d 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -25,8 +25,13 @@
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppB",
manifest: "ScopedStorageTestHelper/TestAppB.xml",
@@ -36,8 +41,13 @@
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppC",
manifest: "ScopedStorageTestHelper/TestAppC.xml",
@@ -47,8 +57,13 @@
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppC30",
manifest: "ScopedStorageTestHelper/TestAppC30.xml",
@@ -58,8 +73,13 @@
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppCLegacy",
manifest: "ScopedStorageTestHelper/TestAppCLegacy.xml",
@@ -69,8 +89,13 @@
min_sdk_version: "28",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppDLegacy",
manifest: "ScopedStorageTestHelper/TestAppDLegacy.xml",
@@ -80,7 +105,11 @@
min_sdk_version: "28",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
android_test_helper_app {
@@ -92,8 +121,13 @@
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppFileManagerBypassDB",
manifest: "ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml",
@@ -103,8 +137,13 @@
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppSystemGalleryBypassDB",
manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml",
@@ -114,8 +153,13 @@
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts",
+ "cts",
+ ],
}
+
android_test_helper_app {
name: "CtsScopedStorageTestAppSystemGallery30BypassDB",
manifest: "ScopedStorageTestHelper/TestAppSystemGallery30BypassDB.xml",
@@ -125,7 +169,11 @@
min_sdk_version: "30",
srcs: ["ScopedStorageTestHelper/src/**/*.java"],
// Tag as a CTS artifact
- test_suites: ["device-tests", "mts", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts",
+ "cts",
+ ],
}
android_test_helper_app {
@@ -150,9 +198,16 @@
name: "ScopedStorageTest",
manifest: "AndroidManifest.xml",
srcs: ["src/**/*.java"],
- static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+ static_libs: [
+ "truth-prebuilt",
+ "cts-scopedstorage-lib",
+ ],
compile_multilib: "both",
- test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
sdk_version: "test_current",
target_sdk_version: "31",
min_sdk_version: "30",
@@ -161,40 +216,67 @@
":CtsScopedStorageTestAppB",
":CtsScopedStorageTestAppC",
":CtsScopedStorageTestAppCLegacy",
- ]
+ ],
}
android_test {
name: "LegacyStorageTest",
manifest: "legacy/AndroidManifest.xml",
srcs: ["legacy/src/**/*.java"],
- static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+ static_libs: [
+ "truth-prebuilt",
+ "cts-scopedstorage-lib",
+ ],
compile_multilib: "both",
- test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
sdk_version: "test_current",
target_sdk_version: "29",
min_sdk_version: "30",
java_resources: [
":CtsScopedStorageTestAppA",
- ]
+ ],
}
java_test_host {
name: "CtsScopedStorageCoreHostTest",
- srcs: [
+ srcs: [
"host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java",
- "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+ "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java",
],
- libs: ["cts-tradefed", "tradefed", "testng"],
- test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "testng",
+ ],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
test_config: "CoreTest.xml",
}
java_test_host {
name: "CtsScopedStorageHostTest",
srcs: ["host/src/**/*.java"],
- libs: ["cts-tradefed", "tradefed", "testng"],
- test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "testng",
+ ],
+ static_libs: [
+ "modules-utils-build-testing",
+ "compatibility-host-util",
+ ],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
test_config: "AndroidTest.xml",
data: [
":CtsLegacyStorageTestAppRequestLegacy",
@@ -205,20 +287,20 @@
java_test_host {
name: "CtsScopedStoragePublicVolumeHostTest",
srcs: ["host/src/**/*.java"],
- libs: ["cts-tradefed", "tradefed", "testng"],
- test_suites: ["general-tests", "mts-mediaprovider"],
- test_config: "PublicVolumeTest.xml",
-}
-
-java_test_host {
- name: "CtsAppCloningHostTest",
- srcs: [
- "host/src/android/scopedstorage/cts/host/AppCloningHostTest.java",
- "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "testng",
],
- libs: ["cts-tradefed", "tradefed", "testng"],
- test_suites: ["general-tests", "mts-mediaprovider", "cts"],
- test_config: "AndroidTestAppCloning.xml",
+ static_libs: [
+ "modules-utils-build-testing",
+ "compatibility-host-util",
+ ],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ ],
+ test_config: "PublicVolumeTest.xml",
}
android_test {
@@ -226,13 +308,24 @@
manifest: "device/AndroidManifest.xml",
test_config: "device/AndroidTest.xml",
srcs: ["device/**/*.java"],
- static_libs: ["truth-prebuilt", "cts-scopedstorage-lib",],
+ static_libs: [
+ "truth-prebuilt",
+ "cts-scopedstorage-lib",
+ ],
compile_multilib: "both",
- test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ test_suites: [
+ "general-tests",
+ "mts-mediaprovider",
+ "cts",
+ ],
sdk_version: "test_current",
target_sdk_version: "31",
min_sdk_version: "30",
- libs: ["android.test.base", "android.test.mock", "android.test.runner",],
+ libs: [
+ "android.test.base",
+ "android.test.mock",
+ "android.test.runner",
+ ],
java_resources: [
":CtsScopedStorageTestAppA",
":CtsScopedStorageTestAppB",
@@ -244,5 +337,5 @@
":CtsScopedStorageTestAppFileManagerBypassDB",
":CtsScopedStorageTestAppSystemGalleryBypassDB",
":CtsScopedStorageTestAppSystemGallery30BypassDB",
- ]
+ ],
}
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index 42a3a36..320d541 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -28,6 +28,12 @@
<option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
<option name="test-file-name" value="CtsLegacyStorageTestAppRequestLegacy.apk" />
</target_preparer>
+
+ <option
+ name="config-descriptor:metadata"
+ key="mainline-param"
+ value="com.google.android.mediaprovider.apex" />
+
<test class="com.android.tradefed.testtype.HostTest" >
<option name="class" value="android.scopedstorage.cts.host.LegacyStorageHostTest" />
<option name="class" value="android.scopedstorage.cts.host.PreserveLegacyStorageHostTest" />
diff --git a/hostsidetests/scopedstorage/AndroidTestAppCloning.xml b/hostsidetests/scopedstorage/AndroidTestAppCloning.xml
deleted file mode 100644
index 03802f2..0000000
--- a/hostsidetests/scopedstorage/AndroidTestAppCloning.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<configuration description="Test for App cloning support with clone user profiles">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <!-- TODO(b/169101565): change to secondary_user when fixed -->
- <!-- Clone user profile is meant to exist only alongside a real system user.
- It does not exist for a headless system user, or a secondary user -->
- <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="class" value="android.scopedstorage.cts.host.AppCloningHostTest" />
- </test>
-
- <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
- </object>
-</configuration>
diff --git a/hostsidetests/scopedstorage/CoreTest.xml b/hostsidetests/scopedstorage/CoreTest.xml
index 5b725e1..325807d 100644
--- a/hostsidetests/scopedstorage/CoreTest.xml
+++ b/hostsidetests/scopedstorage/CoreTest.xml
@@ -27,6 +27,12 @@
<option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
<option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
</target_preparer>
+
+ <option
+ name="config-descriptor:metadata"
+ key="mainline-param"
+ value="com.google.android.mediaprovider.apex" />
+
<test class="com.android.tradefed.testtype.HostTest" >
<option name="class" value="android.scopedstorage.cts.host.ScopedStorageCoreHostTest" />
</test>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index a93aeee..ee63a8a 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -24,6 +24,7 @@
import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.DELETE_RECURSIVE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION;
import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CALLING_PKG;
import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_PATH;
@@ -40,6 +41,7 @@
import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.canOpen;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
@@ -93,6 +95,7 @@
case CAN_READ_WRITE_QUERY:
case CREATE_FILE_QUERY:
case DELETE_FILE_QUERY:
+ case DELETE_RECURSIVE_QUERY:
case CAN_OPEN_FILE_FOR_READ_QUERY:
case CAN_OPEN_FILE_FOR_WRITE_QUERY:
case OPEN_FILE_FOR_READ_QUERY:
@@ -263,6 +266,9 @@
case DELETE_FILE_QUERY:
intent.putExtra(queryType, file.delete());
return intent;
+ case DELETE_RECURSIVE_QUERY:
+ intent.putExtra(queryType, deleteRecursively(file));
+ return intent;
case SETATTR_QUERY:
int newTimeMillis = 12345000;
intent.putExtra(queryType, file.setLastModified(newTimeMillis));
diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml
index 7e6f895..5730b2e 100644
--- a/hostsidetests/scopedstorage/device/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/device/AndroidTest.xml
@@ -23,6 +23,11 @@
<option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" />
</target_preparer>
+ <option
+ name="config-descriptor:metadata"
+ key="mainline-param"
+ value="com.google.android.mediaprovider.apex" />
+
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
index 605f85c..c684ee2 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -29,8 +29,10 @@
import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory;
import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
@@ -43,6 +45,7 @@
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursivelyAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider;
import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
@@ -85,8 +88,10 @@
import static android.scopedstorage.cts.lib.TestUtils.revokePermission;
import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
import static android.scopedstorage.cts.lib.TestUtils.setAttrAs;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
@@ -206,7 +211,7 @@
"CtsScopedStorageTestAppFileManager.apk");
// A legacy targeting app with RES and WES permissions
private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
- "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+ "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk");
// The following apps are not installed at test startup - please install before using.
private static final TestApp APP_C = new TestApp("TestAppC",
@@ -520,7 +525,7 @@
public void testCreateAndDeleteEmptyDir() throws Exception {
final File externalFilesDir = getExternalFilesDir();
// Remove directory in order to create it again
- externalFilesDir.delete();
+ deleteRecursively(externalFilesDir);
// Can create own external files dir
assertThat(externalFilesDir.mkdir()).isTrue();
@@ -534,9 +539,9 @@
assertThat(dir2.mkdir()).isTrue();
// And can delete them all
- assertThat(dir2.delete()).isTrue();
- assertThat(dir1.delete()).isTrue();
- assertThat(externalFilesDir.delete()).isTrue();
+ assertThat(deleteRecursively(dir2)).isTrue();
+ assertThat(deleteRecursively(dir1)).isTrue();
+ assertThat(deleteRecursively(externalFilesDir)).isTrue();
// Can't create external dir for other apps
final File nonexistentPackageFileDir = new File(
@@ -614,7 +619,7 @@
// At this point, we're not sure who created this file, so we'll have both apps
// deleting it
mediaFile.delete();
- dirInDownload.delete();
+ deleteRecursively(dirInDownload);
}
}
@@ -751,7 +756,7 @@
assertThat(dir.list()).asList().doesNotContain(videoFileName);
} finally {
deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getPath());
- dir.delete();
+ deleteRecursively(dir);
}
}
@@ -783,7 +788,7 @@
assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName);
} finally {
deleteFileAsNoThrow(APP_B_NO_PERMS, pdfFile.getPath());
- dir.delete();
+ deleteRecursively(dir);
}
}
@@ -1157,7 +1162,7 @@
try {
// Delete the directory if it already exists
if (podcastsDir.exists()) {
- deleteAsLegacyApp(podcastsDir);
+ deleteRecursivelyAsLegacyApp(podcastsDir);
}
assertThat(podcastsDir.exists()).isFalse();
assertThat(podcastsDirLowerCase.exists()).isFalse();
@@ -1578,7 +1583,7 @@
videoFile1.delete();
videoFile2.delete();
videoFile3.delete();
- nonMediaDir.delete();
+ deleteRecursively(nonMediaDir);
}
}
@@ -1754,15 +1759,15 @@
} finally {
pdfFile.delete();
- nonMediaDirectory.delete();
+ deleteRecursively(nonMediaDirectory);
videoFile1.delete();
videoFile2.delete();
videoFile3.delete();
- mediaDirectory1.delete();
- mediaDirectory2.delete();
- mediaDirectory3.delete();
- mediaDirectory4.delete();
+ deleteRecursively(mediaDirectory1);
+ deleteRecursively(mediaDirectory2);
+ deleteRecursively(mediaDirectory3);
+ deleteRecursively(mediaDirectory4);
}
}
@@ -1788,7 +1793,8 @@
assertThat(deleteFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue();
} finally {
deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getAbsolutePath());
- mediaDirectory1.delete();
+ deleteRecursively(mediaDirectory1);
+ deleteRecursively(mediaDirectory2);
}
}
@@ -1807,8 +1813,8 @@
assertThat(emptyDirectoryOldPath.mkdirs()).isTrue();
assertCanRenameDirectory(emptyDirectoryOldPath, emptyDirectoryNewPath, null, null);
} finally {
- emptyDirectoryOldPath.delete();
- emptyDirectoryNewPath.delete();
+ deleteRecursively(emptyDirectoryOldPath);
+ deleteRecursively(emptyDirectoryNewPath);
}
}
@@ -1932,8 +1938,8 @@
} finally {
hiddenImageFile.delete();
imageFile.delete();
- hiddenDir.delete();
- nonHiddenDir.delete();
+ deleteRecursively(hiddenDir);
+ deleteRecursively(nonHiddenDir);
}
}
@@ -1972,7 +1978,7 @@
noMediaFile.delete();
imageFile.delete();
videoFile.delete();
- directoryNoMedia.delete();
+ deleteRecursively(directoryNoMedia);
}
}
@@ -2057,7 +2063,7 @@
// file.
assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
- trashFile(imageFileUri);
+ trashFileAndAssert(imageFileUri);
// Check that only owner package, file manager and system gallery can list trashed image
// file.
assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
@@ -2066,7 +2072,7 @@
// Check that only owner package, file manager can list pending non media file.
assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
- trashFile(pdfFileUri);
+ trashFileAndAssert(pdfFileUri);
// Check that only owner package, file manager can list trashed non media file.
assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
} finally {
@@ -2198,6 +2204,65 @@
}
@Test
+ public void testSystemGalleryCanTrashOtherAndroidMediaFiles() throws Exception {
+ final File otherVideoFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+ try {
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+ assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+ final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+ assertNotNull(otherVideoUri);
+
+ trashFileAndAssert(otherVideoUri);
+ untrashFileAndAssert(otherVideoUri);
+ } finally {
+ otherVideoFile.delete();
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ @Test
+ public void testSystemGalleryCanUpdateOtherAndroidMediaFiles() throws Exception {
+ final File otherImageFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME));
+ final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+ try {
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+ assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue();
+
+ final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile);
+ assertNotNull(otherImageUri);
+
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+ // Test that we can move the file to "DCIM/"
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ assertThat(updatedImageFileInDcim.exists()).isTrue();
+ assertThat(otherImageFile.exists()).isFalse();
+
+ values.clear();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ "Android/media/" + APP_B_NO_PERMS.getPackageName());
+ // Test that we can move the file back to other app's owned path
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ assertThat(otherImageFile.exists()).isTrue();
+ } finally {
+ otherImageFile.delete();
+ updatedImageFileInDcim.delete();
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ @Test
public void testQueryOtherAppsFiles() throws Exception {
final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
@@ -2291,8 +2356,8 @@
otherAppVideoFile2.delete();
otherAppPdfFile1.delete();
otherAppPdfFile2.delete();
- dirInDcim.delete();
- dirInPictures.delete();
+ deleteRecursively(dirInDcim);
+ deleteRecursively(dirInPictures);
denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
}
}
@@ -2416,8 +2481,8 @@
fileSpecialChars.delete();
fileSpecialChars1.delete();
fileSpecialChars2.delete();
- dirSpecialChars.delete();
- renamedDir.delete();
+ deleteRecursively(dirSpecialChars);
+ deleteRecursively(renamedDir);
}
}
@@ -2487,7 +2552,7 @@
} finally {
deleteAsLegacyApp(topLevelDir1);
deleteAsLegacyApp(topLevelDir2);
- nonTopLevelDir.delete();
+ deleteRecursively(nonTopLevelDir);
}
}
@@ -2725,18 +2790,57 @@
}
}
+ /**
+ * Tests that System Gallery apps cannot insert files in other app's private directories.
+ */
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ int uid = Process.myUid();
+ try {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+ assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ } finally {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ /**
+ * Tests that System Gallery apps cannot update files in other app's private directories.
+ */
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ int uid = Process.myUid();
+ try {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+ assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ } finally {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ /**
+ * This test is for operations to the calling app's own private packages.
+ */
@Test
public void testInsertFromExternalDirsViaRelativePath() throws Exception {
verifyInsertFromExternalMediaDirViaRelativePath_allowed();
verifyInsertFromExternalPrivateDirViaRelativePath_denied();
}
+ /**
+ * This test is for operations to the calling app's own private packages.
+ */
@Test
public void testUpdateToExternalDirsViaRelativePath() throws Exception {
verifyUpdateToExternalMediaDirViaRelativePath_allowed();
verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
}
+ /**
+ * This test is for operations to the calling app's own private packages.
+ */
@Test
public void testInsertFromExternalDirsViaRelativePathAsSystemGallery() throws Exception {
int uid = Process.myUid();
@@ -2749,6 +2853,9 @@
}
}
+ /**
+ * This test is for operations to the calling app's own private packages.
+ */
@Test
public void testUpdateToExternalDirsViaRelativePathAsSystemGallery() throws Exception {
int uid = Process.myUid();
@@ -2999,16 +3106,10 @@
final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile);
assertNotNull(trashedFileUri);
- trashFile(trashedFileUri);
+ trashFileAndAssert(trashedFileUri);
return trashedFileUri;
}
- private void trashFile(Uri uri) throws Exception {
- final ContentValues values = new ContentValues();
- values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
- assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY));
- }
-
/**
* Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to
* multiple db rows, file path is extracted from the first db row of the database query result.
@@ -3281,4 +3382,14 @@
Log.d(TAG, "Deleting file " + file);
deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath());
}
+
+ /**
+ * Deletes the given file/directory recursively. If the file is a directory, then deletes all
+ * of its children (files or directories) recursively.
+ */
+ private void deleteRecursivelyAsLegacyApp(File dir) throws Exception {
+ // Use a legacy app to delete this directory, since it could be outside shared storage.
+ Log.d(TAG, "Deleting directory " + dir);
+ deleteRecursivelyAs(APP_D_LEGACY_HAS_RW, dir.getAbsolutePath());
+ }
}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 3b81646..5638e41 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -111,6 +111,46 @@
}
@Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception {
+ runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasRW");
+ }
+
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception {
+ runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasRW");
+ }
+
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasMES");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasMES");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery");
+ }
+
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery");
+ }
+
+ @Test
public void testMkdirInRandomPlaces_hasW() throws Exception {
revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
executeShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770");
@@ -147,6 +187,11 @@
}
@Test
+ public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception {
+ runDeviceTest("testCanTrashOtherAndroidMediaFiles_hasRW");
+ }
+
+ @Test
public void testCantRename_hasR() throws Exception {
revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
runDeviceTest("testCantRename_hasR");
@@ -214,6 +259,15 @@
runDeviceTest("testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates");
}
+ /**
+ * (b/205673506): Test that legacy System Gallery can update() media file's releative_path to a
+ * non default top level directory.
+ */
+ @Test
+ public void testLegacySystemGalleryCanUpdateToExistingDirectory() throws Exception {
+ runDeviceTest("testLegacySystemGalleryCanUpdateToExistingDirectory");
+ }
+
@Test
public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
@@ -249,4 +303,18 @@
public void testUpdateToExternalDirsViaRelativePath() throws Exception {
runDeviceTest("testUpdateToExternalDirsViaRelativePath");
}
+
+ private void allowAppOps(String... ops) throws Exception {
+ for (String op : ops) {
+ executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy "
+ + op + " allow");
+ }
+ }
+
+ private void denyAppOps(String... ops) throws Exception {
+ for (String op : ops) {
+ executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy "
+ + op + " deny");
+ }
+ }
}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index f1236b6..cd9378d 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -125,6 +125,26 @@
}
@Test
+ public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testManageExternalStorageCantInsertFilesInOtherAppPrivateDir");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
+ public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
public void testCheckInstallerAppAccessToObbDirs() throws Exception {
allowAppOps("android:request_install_packages");
grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
@@ -179,6 +199,26 @@
}
@Test
+ public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testFileManagerCanTrashOtherAndroidMediaFiles");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
+ public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testFileManagerCanUpdateOtherAndroidMediaFiles");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
public void testOpenOtherPendingFilesFromFuse() throws Exception {
grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
try {
diff --git a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
index c602f0a..c85b090 100644
--- a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application android:requestLegacyExternalStorage="true" >
<uses-library android:name="android.test.runner" />
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index 4754d74..07383ac 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -23,7 +23,9 @@
import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
@@ -31,6 +33,7 @@
import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
@@ -38,6 +41,7 @@
import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalStorageDir;
import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
@@ -46,9 +50,13 @@
import static android.scopedstorage.cts.lib.TestUtils.insertFileFromExternalMedia;
import static android.scopedstorage.cts.lib.TestUtils.listAs;
import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
+import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume;
+import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.updateFile;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
@@ -59,6 +67,7 @@
import static androidx.test.InstrumentationRegistry.getContext;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -126,12 +135,14 @@
* test runs.
*/
static final String NONCE = String.valueOf(System.nanoTime());
- static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
+ static final String TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory" + NONCE;
static final String IMAGE_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".jpg";
static final String VIDEO_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".mp4";
static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".pdf";
+ static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
+
// The following apps are installed before the tests are run via a target_preparer.
// See test config for details.
// An app with READ_EXTERNAL_STORAGE permission
@@ -341,7 +352,7 @@
try {
assertThat(newDir.mkdir()).isFalse();
} finally {
- newDir.delete();
+ deleteRecursively(newDir);
}
}
@@ -426,8 +437,25 @@
pdfFile1.delete();
pdfFile2.delete();
- nonMediaDir1.delete();
- nonMediaDir2.delete();
+ deleteRecursively(nonMediaDir1);
+ deleteRecursively(nonMediaDir2);
+ }
+ }
+
+ @Test
+ public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception {
+ final File otherVideoFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+ final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+ assertNotNull(otherVideoUri);
+
+ trashFileAndAssert(otherVideoUri);
+ untrashFileAndAssert(otherVideoUri);
+ } finally {
+ otherVideoFile.delete();
}
}
@@ -521,8 +549,8 @@
// UNIQUE constraint error.
TestUtils.renameWithMediaProvider(directoryOldPath, directoryNewPath);
} finally {
- directoryOldPath.delete();
- directoryNewPath.delete();
+ deleteRecursively(directoryOldPath);
+ deleteRecursively(directoryNewPath);
}
}
@@ -698,7 +726,7 @@
imageInNoMediaDir.delete();
renamedImageInDCIM.delete();
noMediaFile.delete();
- directoryNoMedia.delete();
+ deleteRecursively(directoryNoMedia);
}
}
@@ -852,6 +880,41 @@
}
}
+ /**
+ * (b/205673506): Test that legacy System Gallery can update() media file's releative_path to a
+ * non default top level directory.
+ */
+ @Test
+ public void testLegacySystemGalleryCanUpdateToExistingDirectory() throws Exception {
+ pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+ final File imageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+ // Top level non default directory
+ final File topLevelTestDirectory = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME);
+ final File imageFileInTopLevelDir = new File(topLevelTestDirectory, IMAGE_FILE_NAME);
+ try {
+ assertThat(imageFile.createNewFile()).isTrue();
+ final Uri imageUri = MediaStore.scanFile(getContentResolver(), imageFile);
+ assertThat(imageUri).isNotNull();
+
+ topLevelTestDirectory.mkdirs();
+ assertThat(topLevelTestDirectory.exists()).isTrue();
+
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, topLevelTestDirectory.getName());
+ final int result = getContentResolver().update(imageUri, values, Bundle.EMPTY);
+ assertWithMessage("Result of update() from DCIM -> top level test directory")
+ .that(result).isEqualTo(1);
+ assertThat(imageFileInTopLevelDir.exists()).isTrue();
+ } finally {
+ imageFile.delete();
+ imageFileInTopLevelDir.delete();
+ deleteRecursively(topLevelTestDirectory);
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+ }
+ }
+
@Test
public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
@@ -939,6 +1002,82 @@
}
/**
+ * Tests that legacy apps cannot insert in other app private directory
+ */
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception {
+ pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true);
+ pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true);
+
+ assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
+ /**
+ * Tests that legacy apps cannot update in other app private directory
+ */
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception {
+ pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true);
+ pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true);
+
+ TestUtils.assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
+ /**
+ * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot insert in other app private
+ * directory
+ */
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception {
+ pollForManageExternalStorageAllowed();
+ assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
+ /**
+ * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot update in other app private
+ * directory
+ */
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception {
+ pollForManageExternalStorageAllowed();
+ assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
+ /**
+ * Tests that legacy System Gallery apps cannot insert in other app private directory
+ */
+ @Test
+ public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ int uid = Process.myUid();
+ try {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+ assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ } finally {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ /**
+ * Tests that legacy System Gallery apps cannot update in other app private directory
+ */
+ @Test
+ public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+ int uid = Process.myUid();
+ try {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+ assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ } finally {
+ setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ /**
* Make sure inserting files from app private directories in legacy apps is allowed via DATA.
*/
@Test
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 64e500a..a04b86f 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -16,6 +16,7 @@
package android.scopedstorage.cts.lib;
+import static android.provider.MediaStore.VOLUME_EXTERNAL;
import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY;
import static androidx.test.InstrumentationRegistry.getContext;
@@ -98,6 +99,7 @@
public static final String CREATE_IMAGE_ENTRY_QUERY =
"android.scopedstorage.cts.createimageentry";
public static final String DELETE_FILE_QUERY = "android.scopedstorage.cts.deletefile";
+ public static final String DELETE_RECURSIVE_QUERY = "android.scopedstorage.cts.deleteRecursive";
public static final String CAN_OPEN_FILE_FOR_READ_QUERY =
"android.scopedstorage.cts.can_openfile_read";
public static final String CAN_OPEN_FILE_FOR_WRITE_QUERY =
@@ -295,6 +297,17 @@
}
/**
+ * Makes the given {@code testApp} delete a file or directory.
+ * If the file is a directory, then deletes all of its children (file or directories)
+ * recursively.
+ *
+ * <p>This method drops shell permission identity.
+ */
+ public static boolean deleteRecursivelyAs(TestApp testApp, String path) throws Exception {
+ return getResultFromTestApp(testApp, path, DELETE_RECURSIVE_QUERY);
+ }
+
+ /**
* Makes the given {@code testApp} delete a file. Doesn't throw in case of failure.
*/
public static boolean deleteFileAsNoThrow(TestApp testApp, String path) {
@@ -950,6 +963,111 @@
}
/**
+ * Assert that app cannot insert files in other app's private directories
+ *
+ * @param fileName name of the file
+ * @param throwsExceptionForDataValue Apps like System Gallery for which Data column is not
+ * respected, will not throw an Exception as the Data value is ignored.
+ * @param otherApp Other test app in whose external private directory we will attempt to insert
+ * @param callingPackageName Calling package name
+ */
+ public static void assertCantInsertToOtherPrivateAppDirectories(String fileName,
+ boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName)
+ throws Exception {
+ // Create directory in which the device test will try to insert file to
+ final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+ callingPackageName, otherApp.getPackageName()));
+ final File file = new File(otherAppExternalDataDir, fileName);
+ try {
+ assertThat(createFileAs(otherApp, file.getPath())).isTrue();
+
+ final ContentValues valuesWithData = new ContentValues();
+ valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
+ try {
+ Uri uri = getContentResolver().insert(
+ MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ valuesWithData);
+
+ if (throwsExceptionForDataValue) {
+ fail("File insert expected to fail: " + file);
+ } else {
+ try (Cursor c = getContentResolver().query(uri, new String[]{
+ MediaStore.MediaColumns.DATA}, null, null)) {
+ assertThat(c.moveToFirst()).isTrue();
+ assertThat(c.getString(0)).isNotEqualTo(file.getAbsolutePath());
+ }
+ }
+ } catch (IllegalArgumentException expected) {
+ }
+
+ final ContentValues valuesWithRelativePath = new ContentValues();
+ final String path = file.getAbsolutePath();
+ valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ path.substring(path.indexOf("Android")));
+ valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+ try {
+ getContentResolver().insert(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ valuesWithRelativePath);
+ fail("File insert expected to fail: " + file);
+ } catch (IllegalArgumentException expected) {
+ }
+ } finally {
+ deleteFileAsNoThrow(otherApp, file.getPath());
+ }
+ }
+
+ /**
+ * Assert that app cannot update files in other app's private directories
+ *
+ * @param fileName name of the file
+ * @param throwsExceptionForDataValue Apps like non-legacy System Gallery/MES for which
+ * Data column is not respected, will not throw an Exception as the Data value is ignored.
+ * @param otherApp Other test app in whose external private directory we will attempt to insert
+ * @param callingPackageName Calling package name
+ */
+ public static void assertCantUpdateToOtherPrivateAppDirectories(String fileName,
+ boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName)
+ throws Exception {
+ // Create priv-app file and add to the database that we will try to update
+ final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+ callingPackageName, otherApp.getPackageName()));
+ final File file = new File(otherAppExternalDataDir, fileName);
+ try {
+ assertThat(createFileAs(otherApp, file.getPath())).isTrue();
+ MediaStore.scanFile(getContentResolver(), file);
+
+ final ContentValues valuesWithData = new ContentValues();
+ valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
+ try {
+ int res = getContentResolver().update(
+ MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ valuesWithData, Bundle.EMPTY);
+
+ if (throwsExceptionForDataValue) {
+ fail("File update expected to fail: " + file);
+ } else {
+ assertThat(res).isEqualTo(0);
+ }
+ } catch (IllegalArgumentException expected) {
+ }
+
+ final ContentValues valuesWithRelativePath = new ContentValues();
+ final String path = file.getAbsolutePath();
+ valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ path.substring(path.indexOf("Android")));
+ valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+ try {
+ getContentResolver().update(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ valuesWithRelativePath, Bundle.EMPTY);
+ fail("File update expected to fail: " + file);
+ } catch (IllegalArgumentException expected) {
+ }
+ } finally {
+ deleteFileAsNoThrow(otherApp, file.getPath());
+ }
+ }
+
+ /**
* Asserts can rename directory.
*/
public static void assertCanRenameDirectory(File oldDirectory, File newDirectory,
@@ -1690,4 +1808,21 @@
ActivityManager.class).getRunningAppProcesses().stream().filter(
p -> packageName.equals(p.processName)).findFirst();
}
+
+ public static void trashFileAndAssert(Uri uri) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
+ assertWithMessage("Result of ContentResolver#update for " + uri + " with values to trash "
+ + "file " + values)
+ .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1);
+ }
+
+ public static void untrashFileAndAssert(Uri uri) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.IS_TRASHED, 0);
+ assertWithMessage("Result of ContentResolver#update for " + uri + " with values to untrash "
+ + "file " + values)
+ .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1);
+ }
+
}
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index c915235..ebc8f10 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -21,6 +21,8 @@
import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidDataDir;
import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidObbDir;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
@@ -30,6 +32,7 @@
import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity;
import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
import static android.scopedstorage.cts.lib.TestUtils.getAndroidDir;
@@ -52,6 +55,8 @@
import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaData_denied;
@@ -66,6 +71,7 @@
import static androidx.test.InstrumentationRegistry.getContext;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -76,8 +82,10 @@
import android.Manifest;
import android.app.WallpaperManager;
+import android.content.ContentValues;
import android.net.Uri;
import android.os.Build;
+import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
@@ -122,6 +130,7 @@
static final String AUDIO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp3";
static final String IMAGE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".jpg";
+ static final String VIDEO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp4";
static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".pdf";
// The following apps are installed before the tests are run via a target_preparer.
@@ -136,7 +145,7 @@
"CtsScopedStorageTestAppB.apk");
// A legacy targeting app with RES and WES permissions
private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
- "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+ "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk");
@Before
public void setup() throws Exception {
@@ -222,6 +231,28 @@
});
}
+ /**
+ * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot insert files in other app's
+ * private directories.
+ */
+ @Test
+ public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception {
+ pollForManageExternalStorageAllowed();
+ assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* throwsExceptionForDataValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
+ /**
+ * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot update files in other app's
+ * private directories.
+ */
+ @Test
+ public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception {
+ pollForManageExternalStorageAllowed();
+ assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+ /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+ }
+
@Test
public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception {
pollForManageExternalStorageAllowed();
@@ -299,7 +330,8 @@
final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir");
- final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg");
+ final File otherAppExternalDataFile =
+ new File(otherAppExternalDataSubDir, IMAGE_FILE_NAME);
assertThat(createFileAs(APP_B_NO_PERMS, otherAppExternalDataFile.getAbsolutePath()))
.isTrue();
@@ -489,7 +521,7 @@
nomediaFile.delete();
mediaFile.delete();
renamedMediaFile.delete();
- nomediaDir.delete();
+ deleteRecursively(nomediaDir);
}
}
@@ -526,14 +558,70 @@
mediaFile1InSubDir.delete();
mediaFile2InSubDir.delete();
topLevelNomediaFile.delete();
- nomediaSubDir.delete();
- nomediaDir.delete();
+ deleteRecursively(nomediaSubDir);
+ deleteRecursively(nomediaDir);
// Scan the directory to remove stale db rows.
MediaStore.scanFile(getContentResolver(), nomediaDir);
}
}
@Test
+ public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception {
+ pollForManageExternalStorageAllowed();
+
+ final File otherVideoFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+ final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+ assertNotNull(otherVideoUri);
+
+ trashFileAndAssert(otherVideoUri);
+ untrashFileAndAssert(otherVideoUri);
+ } finally {
+ otherVideoFile.delete();
+ }
+ }
+
+ @Test
+ public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception {
+ pollForManageExternalStorageAllowed();
+
+ final File otherImageFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME));
+ final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue();
+
+ final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile);
+ assertNotNull(otherImageUri);
+
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+ // Test that we can move the file to "DCIM/"
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ assertThat(updatedImageFileInDcim.exists()).isTrue();
+ assertThat(otherImageFile.exists()).isFalse();
+
+ values.clear();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ "Android/media/" + APP_B_NO_PERMS.getPackageName());
+ // Test that we can move the file back to other app's owned path
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ } finally {
+ otherImageFile.delete();
+ updatedImageFileInDcim.delete();
+ }
+ }
+
+ @Test
public void testAndroidMedia() throws Exception {
// Check that the app does not have legacy external storage access
if (isAtLeastS()) {
@@ -818,8 +906,8 @@
imageFile.delete();
renamedImageFile.delete();
imageFileInRenamedDir.delete();
- dir.delete();
- renamedDir.delete();
+ deleteRecursively(dir);
+ deleteRecursively(renamedDir);
}
}
diff --git a/tests/MediaProviderTranscode/Android.bp b/tests/MediaProviderTranscode/Android.bp
index 4ba3243..0a6dfd6 100644
--- a/tests/MediaProviderTranscode/Android.bp
+++ b/tests/MediaProviderTranscode/Android.bp
@@ -7,6 +7,7 @@
test_suites: [
"device-tests",
"cts",
+ "mts-mediaprovider",
],
compile_multilib: "both",
@@ -30,6 +31,7 @@
"truth-prebuilt",
],
+ min_sdk_version: "30",
certificate: "media",
java_resources: [":CtsTranscodeTestAppSupportsHevc", ":CtsTranscodeTestAppSupportsSlowMotion"]
}
@@ -45,6 +47,7 @@
],
static_libs: ["androidx.legacy_legacy-support-v4"],
target_sdk_version: "28",
+ min_sdk_version: "30",
}
android_test_helper_app {
@@ -58,4 +61,5 @@
],
static_libs: ["androidx.legacy_legacy-support-v4"],
target_sdk_version: "28",
+ min_sdk_version: "30",
}
diff --git a/tests/MediaProviderTranscode/AndroidTest.xml b/tests/MediaProviderTranscode/AndroidTest.xml
index 8dba741..6b9c8e9 100644
--- a/tests/MediaProviderTranscode/AndroidTest.xml
+++ b/tests/MediaProviderTranscode/AndroidTest.xml
@@ -19,6 +19,11 @@
<option name="install-arg" value="-g" />
</target_preparer>
+ <option
+ name="config-descriptor:metadata"
+ key="mainline-param"
+ value="com.google.android.mediaprovider.apex" />
+
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="cts" />
<option name="test-tag" value="MediaProviderTranscodeTests" />
@@ -32,4 +37,8 @@
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
</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/tests/PhotoPicker/Android.bp b/tests/PhotoPicker/Android.bp
new file mode 100644
index 0000000..5a9fc33
--- /dev/null
+++ b/tests/PhotoPicker/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsPhotoPickerTest",
+ manifest: "AndroidManifest.xml",
+ test_config: "AndroidTest.xml",
+ srcs: ["src/**/*.java", "helper/**/*.java", ":CtsProviderTestUtils",],
+ compile_multilib: "both",
+ test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+ sdk_version: "core_current",
+ min_sdk_version: "30",
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ "framework-mediaprovider.impl",
+ "framework-res",
+ "android_test_stubs_current"],
+ static_libs: [
+ "androidx.test.rules",
+ "cts-install-lib",
+ "androidx.test.uiautomator_uiautomator",
+ "Harrier",
+ ],
+}
diff --git a/tests/PhotoPicker/AndroidManifest.xml b/tests/PhotoPicker/AndroidManifest.xml
new file mode 100644
index 0000000..ea26ad1
--- /dev/null
+++ b/tests/PhotoPicker/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.photopicker.cts">
+<application android:label="Photo Picker Device Tests">
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.photopicker.cts.GetResultActivity" />
+</application>
+
+<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.photopicker.cts"
+ android:label="Device-only photo picker tests" />
+
+</manifest>
diff --git a/tests/PhotoPicker/AndroidTest.xml b/tests/PhotoPicker/AndroidTest.xml
new file mode 100644
index 0000000..c52543e
--- /dev/null
+++ b/tests/PhotoPicker/AndroidTest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs device-only tests for photo picker">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPhotoPickerTest.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <option name="force-root" value="false" />
+ </target_preparer>
+
+ <option
+ name="config-descriptor:metadata"
+ key="mainline-param"
+ value="com.google.android.mediaprovider.apex" />
+
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="PhotoPickerTests" />
+ <!-- Instant apps cannot access external storage -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.photopicker.cts" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+ <option name="instrumentation-arg" key="thisisignored" value="thisisignored --no-window-animation" />
+ </test>
+ <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+ </object>
+</configuration>
diff --git a/tests/PhotoPicker/OWNERS b/tests/PhotoPicker/OWNERS
new file mode 100644
index 0000000..79add9e
--- /dev/null
+++ b/tests/PhotoPicker/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 95221
+
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/tests/PhotoPicker/TEST_MAPPING b/tests/PhotoPicker/TEST_MAPPING
new file mode 100644
index 0000000..f48e90c
--- /dev/null
+++ b/tests/PhotoPicker/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPhotoPickerTest"
+ }
+ ]
+}
diff --git a/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg
new file mode 100644
index 0000000..d264196
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg
Binary files differ
diff --git a/tests/PhotoPicker/res/raw/test_video.mp4 b/tests/PhotoPicker/res/raw/test_video.mp4
new file mode 100644
index 0000000..ab95ac0
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/test_video.mp4
Binary files differ
diff --git a/tests/PhotoPicker/res/raw/test_video_dng.mp4 b/tests/PhotoPicker/res/raw/test_video_dng.mp4
new file mode 100644
index 0000000..9b38f0e
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/test_video_dng.mp4
Binary files differ
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java
new file mode 100644
index 0000000..35e7830
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GetResultActivity extends Activity {
+ private static LinkedBlockingQueue<Result> sResult;
+
+ public static class Result {
+ public final int requestCode;
+ public final int resultCode;
+ public final Intent data;
+
+ public Result(int requestCode, int resultCode, Intent data) {
+ this.requestCode = requestCode;
+ this.resultCode = resultCode;
+ this.data = data;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ try {
+ sResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void clearResult() {
+ sResult = new LinkedBlockingQueue<>();
+ }
+
+ public Result getResult() {
+ final Result result;
+ try {
+ result = sResult.poll(40, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ if (result == null) {
+ throw new IllegalStateException("Activity didn't receive a Result in 40 seconds");
+ }
+ return result;
+ }
+
+ public Result getResult(long timeout, TimeUnit unit) {
+ try {
+ return sResult.poll(timeout, unit);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java
new file mode 100644
index 0000000..79d4b2b
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts;
+
+import android.Manifest;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.DeviceConfig;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.Before;
+
+/**
+ * Photo Picker Base class for Photo Picker tests. This includes common setup methods
+ * required for all Photo Picker tests.
+ */
+public class PhotoPickerBaseTest {
+ public static int REQUEST_CODE = 42;
+
+ protected GetResultActivity mActivity;
+ protected Context mContext;
+ protected UiDevice mDevice;
+
+ @Before
+ public void setUp() throws Exception {
+ final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+ mDevice = UiDevice.getInstance(inst);
+
+ final String setSyncDelayCommand =
+ "device_config put storage pickerdb.default_sync_delay_ms 0";
+ mDevice.executeShellCommand(setSyncDelayCommand);
+
+ mContext = inst.getContext();
+ final Intent intent = new Intent(mContext, GetResultActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+ // Wake up the device and dismiss the keyguard before the test starts
+ mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ mDevice.executeShellCommand("wm dismiss-keyguard");
+
+ mActivity = (GetResultActivity) inst.startActivitySync(intent);
+ // Wait for the UI Thread to become idle.
+ inst.waitForIdleSync();
+ mActivity.clearResult();
+ mDevice.waitForIdle();
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
new file mode 100644
index 0000000..1092406
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts;
+
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findProfileButton;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiSelector;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Device only tests for cross profile interaction flows.
+ */
+@RunWith(BedsteadJUnit4.class)
+public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest {
+ @ClassRule @Rule
+ public static final DeviceState sDeviceState = new DeviceState();
+
+ private List<Uri> mUriList = new ArrayList<>();
+
+ @After
+ public void tearDown() throws Exception {
+ for (Uri uri : mUriList) {
+ deleteMedia(uri, mContext);
+ }
+ mUriList.clear();
+ mActivity.finish();
+ }
+
+ /**
+ * ACTION_PICK_IMAGES is allowlisted by default from work to personal. This got allowlisted
+ * in a platform code change and is available Android T onwards.
+ */
+ @Test
+ @RequireRunOnWorkProfile
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWorkApp_canAccessPersonalProfileContents() throws Exception {
+ final int imageCount = 2;
+ createImages(imageCount, sDeviceState.primaryUser().id(), mUriList);
+
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ // Click the profile button to change to personal profile
+ final UiObject profileButton = findProfileButton();
+ profileButton.click();
+ mDevice.waitForIdle();
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ for (int i = 0; i < itemCount; i++) {
+ final UiObject item = itemList.get(i);
+ item.click();
+ mDevice.waitForIdle();
+ }
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(imageCount);
+ for (int i = 0; i < count; i++) {
+ Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, sDeviceState.primaryUser().id());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+
+ /**
+ * ACTION_PICK_IMAGES is allowlisted by default from work to personal. This got allowlisted
+ * in a platform code change and is available Android T onwards. Before that it needs to be
+ * explicitly allowlisted by the device admin.
+ */
+ @Test
+ @RequireRunOnWorkProfile
+ @SdkSuppress(maxSdkVersion = 31, codeName = "S")
+ public void testWorkApp_cannotAccessPersonalProfile_beforeT() throws Exception {
+ assertBlockedByAdmin(/* isInvokedFromWorkProfile */ true);
+ }
+
+ /**
+ * ACTION_PICK_IMAGES is allowlisted by default from work to personal only (not vice-a-versa)
+ */
+ @Test
+ @EnsureHasWorkProfile
+ public void testPersonalApp_cannotAccessWorkProfile_default() throws Exception {
+ assertBlockedByAdmin(/* isInvokedFromWorkProfile */ false);
+ }
+
+ private void assertBlockedByAdmin(boolean isInvokedFromWorkProfile) throws Exception {
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ // Click the profile button to change to work profile
+ final UiObject profileButton = findProfileButton();
+ profileButton.click();
+ mDevice.waitForIdle();
+
+ assertBlockedByAdminDialog(isInvokedFromWorkProfile);
+ }
+
+ private void assertBlockedByAdminDialog(boolean isInvokedFromWorkProfile) {
+ final String dialogTitle = "Blocked by your admin";
+ assertWithMessage("Timed out while waiting for blocked by admin dialog to appear")
+ .that(new UiObject(new UiSelector().textContains(dialogTitle))
+ .waitForExists(SHORT_TIMEOUT))
+ .isTrue();
+
+ final String dialogDescription;
+ if (isInvokedFromWorkProfile) {
+ dialogDescription = "Accessing personal data from a work app is not permitted";
+ } else {
+ dialogDescription = "Accessing work data from a personal app is not permitted";
+ }
+ assertWithMessage("Blocked by admin description is not as expected")
+ .that(new UiObject(new UiSelector().textContains(dialogDescription)).exists())
+ .isTrue();
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
new file mode 100644
index 0000000..92c96a6
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts;
+
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertMimeType;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPersistedGrant;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createDNGVideos;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideos;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddOrSelectButton;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.After;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Device only tests for common flows.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PhotoPickerTest extends PhotoPickerBaseTest {
+ private List<Uri> mUriList = new ArrayList<>();
+
+ @After
+ public void tearDown() throws Exception {
+ for (Uri uri : mUriList) {
+ deleteMedia(uri, mContext);
+ }
+ mUriList.clear();
+
+ if (mActivity != null) {
+ mActivity.finish();
+ }
+ }
+
+ @Test
+ public void testSingleSelect() throws Exception {
+ final int itemCount = 1;
+ createImages(itemCount, mContext.getUserId(), mUriList);
+
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final UiObject item = findItemList(itemCount).get(0);
+ clickAndWait(item);
+
+ final Uri uri = mActivity.getResult().data.getData();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertPersistedGrant(uri, mContext.getContentResolver());
+ assertRedactedReadOnlyAccess(uri);
+ }
+
+ @Test
+ public void testSingleSelectForFavoritesAlbum() throws Exception {
+ final int itemCount = 1;
+ createImages(itemCount, mContext.getUserId(), mUriList, true);
+
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ UiObject albumsTab = mDevice.findObject(new UiSelector().text(
+ "Albums"));
+ clickAndWait(albumsTab);
+ final UiObject album = findItemList(1).get(0);
+ clickAndWait(album);
+
+ final UiObject item = findItemList(itemCount).get(0);
+ clickAndWait(item);
+
+ final Uri uri = mActivity.getResult().data.getData();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+
+ @Test
+ public void testLaunchPreviewMultipleForVideoAlbum() throws Exception {
+ final int videoCount = 2;
+ createVideos(videoCount, mContext.getUserId(), mUriList);
+
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.setType("video/*");
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ UiObject albumsTab = mDevice.findObject(new UiSelector().text(
+ "Albums"));
+ clickAndWait(albumsTab);
+ final UiObject album = findItemList(1).get(0);
+ clickAndWait(album);
+
+ final List<UiObject> itemList = findItemList(videoCount);
+ final int itemCount = itemList.size();
+
+ assertThat(itemCount).isEqualTo(videoCount);
+
+ for (int i = 0; i < itemCount; i++) {
+ clickAndWait(itemList.get(i));
+ }
+
+ clickAndWait(findViewSelectedButton());
+
+ // Wait for playback to start. This is needed in some devices where playback
+ // buffering -> ready state takes around 10s.
+ final long playbackStartTimeout = 10000;
+ (findPreviewVideoImageView()).waitUntilGone(playbackStartTimeout);
+ }
+
+ @Test
+ public void testSingleSelectWithPreview() throws Exception {
+ final int itemCount = 1;
+ createImages(itemCount, mContext.getUserId(), mUriList);
+
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final UiObject item = findItemList(itemCount).get(0);
+ item.longClick();
+ mDevice.waitForIdle();
+
+ final UiObject addButton = findPreviewAddOrSelectButton();
+ assertThat(addButton.waitForExists(1000)).isTrue();
+ clickAndWait(addButton);
+
+ final Uri uri = mActivity.getResult().data.getData();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+
+ @Test
+ public void testMultiSelect_invalidParam() throws Exception {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit() + 1);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ final GetResultActivity.Result res = mActivity.getResult();
+ assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void testMultiSelect_invalidNegativeParam() throws Exception {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, -1);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ final GetResultActivity.Result res = mActivity.getResult();
+ assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void testMultiSelect_returnsNotMoreThanMax() throws Exception {
+ final int maxCount = 2;
+ final int imageCount = maxCount + 1;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ // Select maxCount + 1 item
+ for (int i = 0; i < itemCount; i++) {
+ clickAndWait(itemList.get(i));
+ }
+
+ UiObject snackbarTextView = mDevice.findObject(new UiSelector().text(
+ "Select up to 2 items"));
+ assertWithMessage("Timed out while waiting for snackbar to appear").that(
+ snackbarTextView.waitForExists(SHORT_TIMEOUT)).isTrue();
+
+ assertWithMessage("Timed out waiting for snackbar to disappear").that(
+ snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue();
+
+ clickAndWait(findAddButton());
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(maxCount);
+ }
+
+ @Test
+ public void testDoesNotRespectExtraAllowMultiple() throws Exception {
+ final int imageCount = 2;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ // Select 1 item
+ clickAndWait(itemList.get(0));
+
+ final Uri uri = mActivity.getResult().data.getData();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertPersistedGrant(uri, mContext.getContentResolver());
+ assertRedactedReadOnlyAccess(uri);
+ }
+
+ @Test
+ public void testMultiSelect() throws Exception {
+ final int imageCount = 4;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ for (int i = 0; i < itemCount; i++) {
+ clickAndWait(itemList.get(i));
+ }
+
+ clickAndWait(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 uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertPersistedGrant(uri, mContext.getContentResolver());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+
+ @Test
+ public void testMultiSelect_longPress() throws Exception {
+ final int videoCount = 3;
+ createDNGVideos(videoCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+ intent.setType("video/*");
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(videoCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(videoCount);
+
+ // Select one item from Photo grid
+ clickAndWait(itemList.get(0));
+
+ // Preview the item
+ UiObject item = itemList.get(1);
+ item.longClick();
+ mDevice.waitForIdle();
+
+ final UiObject addOrSelectButton = findPreviewAddOrSelectButton();
+ assertWithMessage("Timed out waiting for AddOrSelectButton to appear")
+ .that(addOrSelectButton.waitForExists(1000)).isTrue();
+
+ // Select the item from Preview
+ clickAndWait(addOrSelectButton);
+
+ mDevice.pressBack();
+
+ // Select one more item from Photo grid
+ clickAndWait(itemList.get(2));
+
+ clickAndWait(findAddButton());
+
+ // Verify that all 3 items are returned
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(3);
+ for (int i = 0; i < count; i++) {
+ final Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertPersistedGrant(uri, mContext.getContentResolver());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+
+ @Test
+ public void testMultiSelect_preview() throws Exception {
+ final int imageCount = 4;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ for (int i = 0; i < itemCount; i++) {
+ clickAndWait(itemList.get(i));
+ }
+
+ clickAndWait(findViewSelectedButton());
+
+ // Swipe left three times
+ swipeLeftAndWait();
+ swipeLeftAndWait();
+ swipeLeftAndWait();
+
+ // Deselect one item
+ clickAndWait(findPreviewSelectedCheckButton());
+
+ // Return selected items
+ clickAndWait(findPreviewAddButton());
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(itemCount - 1);
+ for (int i = 0; i < count; i++) {
+ final Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertPersistedGrant(uri, mContext.getContentResolver());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+
+ @Test
+ @Ignore("Re-enable once we find work around for b/226318844")
+ public void testMultiSelect_previewVideoPlayPause() throws Exception {
+ launchPreviewMultipleWithVideos(/* videoCount */ 3);
+
+ // Check Play/Pause in first video
+ testVideoPreviewPlayPause();
+
+ // Move to third video
+ swipeLeftAndWait();
+ swipeLeftAndWait();
+ // Check Play/Pause in third video
+ testVideoPreviewPlayPause();
+
+ // We don't test the result of the picker here because the intention of the test is only to
+ // test the video controls
+ }
+
+ @Test
+ public void testMultiSelect_previewVideoMuteButtonInitial() throws Exception {
+ launchPreviewMultipleWithVideos(/* videoCount */ 1);
+
+ final UiObject playPauseButton = findPlayPauseButton();
+ final UiObject muteButton = findMuteButton();
+
+ // check that player controls are visible
+ assertPlayerControlsVisible(playPauseButton, muteButton);
+
+ // Test 1: Initial state of the mute Button
+ // Check that initial state of mute button is mute, i.e., volume off
+ assertMuteButtonState(muteButton, /* isMuted */ true);
+
+ // Test 2: Click Mute Button
+ // Click to unmute the audio
+ clickAndWait(muteButton);
+ // Check that mute button state is unmute, i.e., it shows `volume up` icon
+ assertMuteButtonState(muteButton, /* isMuted */ false);
+ // Click on the muteButton and check that mute button status is now 'mute'
+ clickAndWait(muteButton);
+ assertMuteButtonState(muteButton, /* isMuted */ true);
+ // Click on the muteButton and check that mute button status is now unmute
+ clickAndWait(muteButton);
+ assertMuteButtonState(muteButton, /* isMuted */ false);
+
+ // Test 3: Next preview resumes mute state
+ // Go back and launch preview again
+ mDevice.pressBack();
+ clickAndWait(findViewSelectedButton());
+
+ // check that player controls are visible
+ assertPlayerControlsVisible(playPauseButton, muteButton);
+ assertMuteButtonState(muteButton, /* isMuted */ false);
+
+ // We don't test the result of the picker here because the intention of the test is only to
+ // test the video controls
+ }
+
+ @Test
+ public void testMultiSelect_previewVideoMuteButtonOnSwipe() throws Exception {
+ launchPreviewMultipleWithVideos(/* videoCount */ 3);
+
+ final UiObject playPauseButton = findPlayPauseButton();
+ final UiObject muteButton = findMuteButton();
+ final UiObject playerView = findPlayerView();
+
+ // check that player controls are visible
+ assertPlayerControlsVisible(playPauseButton, muteButton);
+
+ // Test 1: Swipe resumes mute state, with state of the button is 'volume off' / 'mute'
+ assertMuteButtonState(muteButton, /* isMuted */ true);
+ // Swipe to next page and check that muteButton is in mute state.
+ swipeLeftAndWait();
+ // set-up and wait for player controls to be sticky
+ setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton);
+ assertMuteButtonState(muteButton, /* isMuted */ true);
+
+ // Test 2: Swipe resumes mute state, with state of mute button 'volume up' / 'unmute'
+ // Click muteButton again to check the next video resumes the previous video's mute state
+ clickAndWait(muteButton);
+ assertMuteButtonState(muteButton, /* isMuted */ false);
+ // check that next video resumed previous video's mute state
+ swipeLeftAndWait();
+ // check that player controls are visible
+ assertPlayerControlsVisible(playPauseButton, muteButton);
+ assertMuteButtonState(muteButton, /* isMuted */ false);
+
+ // We don't test the result of the picker here because the intention of the test is only to
+ // test the video controls
+ }
+
+ @Test
+ @Ignore("Re-enable once we find work around for b/226318844")
+ public void testMultiSelect_previewVideoControlsVisibility() throws Exception {
+ launchPreviewMultipleWithVideos(/* videoCount */ 3);
+
+ mDevice.waitForIdle();
+
+ final UiObject playPauseButton = findPlayPauseButton();
+ final UiObject muteButton = findMuteButton();
+ // Check that buttons auto hide.
+ assertPlayerControlsAutoHide(playPauseButton, muteButton);
+
+ final UiObject playerView = findPlayerView();
+ // Click on StyledPlayerView to make the video controls visible
+ clickAndWait(playerView);
+ assertPlayerControlsVisible(playPauseButton, muteButton);
+
+ // Wait for 1s and check that controls are still visible
+ assertPlayerControlsDontAutoHide(playPauseButton, muteButton);
+
+ // Click on StyledPlayerView and check that controls are no longer visible. Don't click in
+ // the center, clicking in the center may pause the video.
+ playerView.clickBottomRight();
+ mDevice.waitForIdle();
+ assertPlayerControlsHidden(playPauseButton, muteButton);
+
+ // Swipe left and check that controls are not visible
+ swipeLeftAndWait();
+ assertPlayerControlsHidden(playPauseButton, muteButton);
+
+ // Click on the StyledPlayerView and check that controls appear
+ clickAndWait(playerView);
+ assertPlayerControlsVisible(playPauseButton, muteButton);
+
+ // Swipe left to check that controls are now visible on swipe
+ swipeLeftAndWait();
+ assertPlayerControlsVisible(playPauseButton, muteButton);
+
+ // Check that the player controls are auto hidden in 1s
+ assertPlayerControlsAutoHide(playPauseButton, muteButton);
+
+ // We don't test the result of the picker here because the intention of the test is only to
+ // test the video controls
+ }
+
+ @Test
+ public void testMimeTypeFilter() throws Exception {
+ final int videoCount = 2;
+ createDNGVideos(videoCount, mContext.getUserId(), mUriList);
+ final int imageCount = 1;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+
+ final String mimeType = "video/dng";
+
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+ intent.setType(mimeType);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ // find all items
+ final List<UiObject> itemList = findItemList(-1);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isAtLeast(videoCount);
+ for (int i = 0; i < itemCount; i++) {
+ clickAndWait(itemList.get(i));
+ }
+
+ clickAndWait(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 uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertPersistedGrant(uri, mContext.getContentResolver());
+ assertRedactedReadOnlyAccess(uri);
+ assertMimeType(uri, mimeType);
+ }
+ }
+
+ private void assertMuteButtonState(UiObject muteButton, boolean isMuted)
+ throws UiObjectNotFoundException {
+ // We use content description to assert the state of the mute button, there is no other way
+ // to test this.
+ final String expectedContentDescription = isMuted ? "Unmute video" : "Mute video";
+ final String assertMessage =
+ "Expected mute button content description to be " + expectedContentDescription;
+ assertWithMessage(assertMessage).that(muteButton.getContentDescription())
+ .isEqualTo(expectedContentDescription);
+ }
+
+ private void testVideoPreviewPlayPause() throws Exception {
+ final UiObject playPauseButton = findPlayPauseButton();
+ final UiObject muteButton = findMuteButton();
+
+ // Wait for buttons to auto hide.
+ assertPlayerControlsAutoHide(playPauseButton, muteButton);
+
+ // Click on StyledPlayerView to make the video controls visible
+ clickAndWait(findPlayerView());
+
+ // PlayPause button is now pause button, click the button to pause the video.
+ clickAndWait(playPauseButton);
+
+ // Wait for 1s and check that play button is not auto hidden
+ assertPlayerControlsDontAutoHide(playPauseButton, muteButton);
+
+ // PlayPause button is now play button, click the button to play the video.
+ clickAndWait(playPauseButton);
+ // Check that pause button auto-hides in 1s.
+ assertPlayerControlsAutoHide(playPauseButton, muteButton);
+ }
+
+ private void launchPreviewMultipleWithVideos(int videoCount) throws Exception {
+ createVideos(videoCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+ intent.setType("video/*");
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(videoCount);
+ final int itemCount = itemList.size();
+
+ assertThat(itemCount).isEqualTo(videoCount);
+
+ for (int i = 0; i < itemCount; i++) {
+ clickAndWait(itemList.get(i));
+ }
+
+ clickAndWait(findViewSelectedButton());
+
+ // Wait for playback to start. This is needed in some devices where playback
+ // buffering -> ready state takes around 10s.
+ final long playbackStartTimeout = 10000;
+ (findPreviewVideoImageView()).waitUntilGone(playbackStartTimeout);
+ }
+
+ private void setUpAndAssertStickyPlayerControls(UiObject playerView, UiObject playPauseButton,
+ UiObject muteButton) throws Exception {
+ // Wait for 1s or Play/Pause button to hide
+ playPauseButton.waitUntilGone(1000);
+ // Click on StyledPlayerView to make the video controls visible
+ clickAndWait(playerView);
+ assertPlayerControlsVisible(playPauseButton, muteButton);
+ }
+
+ private void assertPlayerControlsVisible(UiObject playPauseButton, UiObject muteButton) {
+ assertVisible(playPauseButton, "Expected play/pause button to be visible");
+ assertVisible(muteButton, "Expected mute button to be visible");
+ }
+
+ private void assertPlayerControlsHidden(UiObject playPauseButton, UiObject muteButton) {
+ assertHidden(playPauseButton, "Expected play/pause button to be hidden");
+ assertHidden(muteButton, "Expected mute button to be hidden");
+ }
+
+ private void assertPlayerControlsAutoHide(UiObject playPauseButton, UiObject muteButton) {
+ // These buttons should auto hide in 1 second after the video playback start. Since we can't
+ // identify the video playback start time, we wait for 2 seconds instead.
+ assertWithMessage("Expected play/pause button to auto hide in 2s")
+ .that(playPauseButton.waitUntilGone(2000)).isTrue();
+ assertHidden(muteButton, "Expected mute button to hide after 2s");
+ }
+
+ private void assertPlayerControlsDontAutoHide(UiObject playPauseButton, UiObject muteButton) {
+ assertWithMessage("Expected play/pause button to not auto hide in 1s")
+ .that(playPauseButton.waitUntilGone(1100)).isFalse();
+ assertVisible(muteButton, "Expected mute button to be still visible after 1s");
+ }
+
+ private void assertVisible(UiObject button, String message) {
+ assertWithMessage(message).that(button.exists()).isTrue();
+ }
+
+ private void assertHidden(UiObject button, String message) {
+ assertWithMessage(message).that(button.exists()).isFalse();
+ }
+
+ private static UiObject findViewSelectedButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/button_view_selected"));
+ }
+
+ private static UiObject findPreviewSelectedCheckButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/preview_selected_check_button"));
+ }
+
+
+ private static UiObject findPlayerView() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/preview_player_view"));
+ }
+
+ private static UiObject findMuteButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/preview_mute"));
+ }
+
+ private static UiObject findPlayPauseButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/exo_play_pause"));
+ }
+
+ private static UiObject findPreviewVideoImageView() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/preview_video_image"));
+ }
+
+ private void clickAndWait(UiObject uiObject) throws Exception {
+ uiObject.click();
+ mDevice.waitForIdle();
+ }
+
+ private void swipeLeftAndWait() {
+ final int width = mDevice.getDisplayWidth();
+ final int height = mDevice.getDisplayHeight();
+ mDevice.swipe(15 * width / 20, height / 2, width / 20, height / 2, 10);
+ mDevice.waitForIdle();
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
new file mode 100644
index 0000000..d0aec7d
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriPermission;
+import android.database.Cursor;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore.PickerMediaColumns;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for test assertions.
+ */
+public class PhotoPickerAssertionsUtils {
+ private static final String TAG = "PhotoPickerTestAssertions";
+
+ public static void assertPickerUriFormat(Uri uri, int expectedUserId) {
+ // content://media/picker/<user-id>/<media-id>
+ final int userId = Integer.parseInt(uri.getPathSegments().get(1));
+ assertThat(userId).isEqualTo(expectedUserId);
+
+ final String auth = uri.getPathSegments().get(0);
+ assertThat(auth).isEqualTo("picker");
+ }
+
+ public static void assertPersistedGrant(Uri uri, ContentResolver resolver) {
+ resolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
+ final List<Uri> uris = new ArrayList<>();
+ for (UriPermission perm : uriPermissions) {
+ if (perm.isReadPermission()) {
+ uris.add(perm.getUri());
+ }
+ }
+
+ assertThat(uris).contains(uri);
+ }
+
+ public static void assertMimeType(Uri uri, String expectedMimeType) throws Exception {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final String resultMimeType = context.getContentResolver().getType(uri);
+ assertThat(resultMimeType).isEqualTo(expectedMimeType);
+ }
+
+ public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception {
+ assertThat(uri).isNotNull();
+ final String[] projection = new String[]{ PickerMediaColumns.MIME_TYPE };
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final ContentResolver resolver = context.getContentResolver();
+ try (Cursor c = resolver.query(uri, projection, null, null)) {
+ assertThat(c).isNotNull();
+ assertThat(c.moveToFirst()).isTrue();
+
+ final String mimeType = c.getString(c.getColumnIndex(PickerMediaColumns.MIME_TYPE));
+
+ if (mimeType.startsWith("image")) {
+ assertImageRedactedReadOnlyAccess(uri, resolver);
+ } else if (mimeType.startsWith("video")) {
+ assertVideoRedactedReadOnlyAccess(uri, resolver);
+ } else {
+ fail("The mime type is not as expected: " + mimeType);
+ }
+ }
+ }
+
+ private static void assertVideoRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
+ throws Exception {
+ // The location is redacted
+ // TODO(b/201505595): Make this method work for test_video.mp4. Currently it works only for
+ // test_video_dng.mp4
+ try (InputStream in = resolver.openInputStream(uri);
+ ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ FileUtils.copy(in, out);
+ byte[] bytes = out.toByteArray();
+ byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
+ String xmp = new String(xmpBytes);
+ assertWithMessage("Failed to redact XMP longitude")
+ .that(xmp.contains("10,41.751000E")).isFalse();
+ assertWithMessage("Failed to redact XMP latitude")
+ .that(xmp.contains("53,50.070500N")).isFalse();
+ assertWithMessage("Redacted non-location XMP")
+ .that(xmp.contains("13166/7763")).isTrue();
+ }
+
+ // assert no write access
+ try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+ fail("Does not grant write access to uri " + uri.toString());
+ } catch (SecurityException | FileNotFoundException expected) {
+ }
+ }
+
+ private static void assertImageRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
+ throws Exception {
+ // The location is redacted
+ try (InputStream is = resolver.openInputStream(uri)) {
+ final ExifInterface exif = new ExifInterface(is);
+ final float[] latLong = new float[2];
+ exif.getLatLong(latLong);
+ assertWithMessage("Failed to redact latitude")
+ .that(latLong[0]).isWithin(0.001f).of(0);
+ assertWithMessage("Failed to redact longitude")
+ .that(latLong[1]).isWithin(0.001f).of(0);
+
+ String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
+ assertWithMessage("Failed to redact XMP longitude")
+ .that(xmp.contains("10,41.751000E")).isFalse();
+ assertWithMessage("Failed to redact XMP latitude")
+ .that(xmp.contains("53,50.070500N")).isFalse();
+ assertWithMessage("Redacted non-location XMP")
+ .that(xmp.contains("LensDefaults")).isTrue();
+ }
+
+ // assert no write access
+ try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+ fail("Does not grant write access to uri " + uri.toString());
+ } catch (SecurityException | FileNotFoundException expected) {
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
new file mode 100644
index 0000000..3705ddd
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts.util;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.photopicker.cts.R;
+import android.provider.MediaStore;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreUtils;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for media files creation and deletion.
+ */
+public class PhotoPickerFilesUtils {
+ public static final String DISPLAY_NAME_PREFIX = "ctsPhotoPicker";
+
+ public static void createImages(int count, int userId, List<Uri> uriList)
+ throws Exception {
+ createImages(count, userId, uriList, false);
+ }
+
+ public static void createImages(int count, int userId, List<Uri> uriList, boolean isFavorite)
+ throws Exception {
+ for (int i = 0; i < count; i++) {
+ final Uri uri = createImage(userId, isFavorite);
+ uriList.add(uri);
+ clearMediaOwner(uri, userId);
+ }
+ // Wait for Picker db sync to complete
+ MediaStore.waitForIdle(InstrumentationRegistry.getContext().getContentResolver());
+ }
+
+ public static void createDNGVideos(int count, int userId, List<Uri> uriList)
+ throws Exception {
+ for (int i = 0; i < count; i++) {
+ final Uri uri = createDNGVideo(userId);
+ uriList.add(uri);
+ clearMediaOwner(uri, userId);
+ }
+ // Wait for Picker db sync to complete
+ MediaStore.waitForIdle(InstrumentationRegistry.getContext().getContentResolver());
+ }
+
+ public static void createVideos(int count, int userId, List<Uri> uriList)
+ throws Exception {
+ for (int i = 0; i < count; i++) {
+ final Uri uri = createVideo(userId);
+ uriList.add(uri);
+ clearMediaOwner(uri, userId);
+ }
+ // Wait for Picker db sync to complete
+ MediaStore.waitForIdle(InstrumentationRegistry.getContext().getContentResolver());
+ }
+
+ public static void deleteMedia(Uri uri, Context context) throws Exception {
+ try {
+ ProviderTestUtils.setOwner(uri, context.getPackageName());
+ context.getContentResolver().delete(uri, Bundle.EMPTY);
+ } catch (Exception ignored) {
+ }
+ }
+
+ private static void clearMediaOwner(Uri uri, int userId) throws Exception {
+ final String cmd = String.format(
+ "content update --uri %s --user %d --bind owner_package_name:n:", uri, userId);
+ ShellUtils.runShellCommand(cmd);
+ }
+
+ private static Uri createDNGVideo(int userId) throws Exception {
+ final Uri uri = stageMedia(R.raw.test_video_dng,
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId);
+ return uri;
+ }
+
+ private static Uri createVideo(int userId) throws Exception {
+ final Uri uri = stageMedia(R.raw.test_video,
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId);
+ return uri;
+ }
+
+ private static Uri createImage(int userId, boolean isFavorite) throws Exception {
+ final Uri uri = stageMedia(R.raw.lg_g4_iso_800_jpg,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg", userId, isFavorite);
+ return uri;
+ }
+
+ private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, int userId,
+ boolean isFavorite) throws
+ Exception {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity(
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ try {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final Context userContext = userId == context.getUserId() ? context :
+ context.createPackageContextAsUser("android", /* flags= */ 0,
+ UserHandle.of(userId));
+ return stageMedia(resId, collectionUri, mimeType, userContext, isFavorite);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, int userId) throws
+ Exception {
+ return stageMedia(resId, collectionUri, mimeType, userId, false);
+ }
+
+ private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, Context context,
+ boolean isFavorite)
+ throws IOException {
+ final String displayName = DISPLAY_NAME_PREFIX + System.nanoTime();
+ final MediaStoreUtils.PendingParams params = new MediaStoreUtils.PendingParams(
+ collectionUri, displayName, mimeType);
+ params.setIsFavorite(isFavorite);
+ final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+ try (MediaStoreUtils.PendingSession session = MediaStoreUtils.openPending(context,
+ pendingUri)) {
+ try (InputStream source = context.getResources().openRawResource(resId);
+ OutputStream target = session.openOutputStream()) {
+ FileUtils.copy(source, target);
+ }
+ return session.publish();
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
new file mode 100644
index 0000000..d20dcd6
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.photopicker.cts.util;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.text.format.DateUtils;
+
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for finding UI elements.
+ */
+public class PhotoPickerUiUtils {
+ public static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS;
+
+ private static final long TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS;
+ public static final String REGEX_PACKAGE_NAME =
+ "com(.google)?.android.providers.media(.module)?";
+
+ /**
+ * Get the list of items from the photo grid list.
+ * @param itemCount if the itemCount is -1, return all matching items. Otherwise, return the
+ * item list that its size is not greater than the itemCount.
+ * @throws Exception
+ */
+ public static List<UiObject> findItemList(int itemCount) throws Exception {
+ final List<UiObject> itemList = new ArrayList<>();
+ final UiSelector gridList = new UiSelector().className(
+ "androidx.recyclerview.widget.RecyclerView").resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/picker_tab_recyclerview");
+
+ // Wait for the first item to appear
+ assertWithMessage("Timed out while waiting for first item to appear")
+ .that(new UiObject(gridList.childSelector(new UiSelector())).waitForExists(TIMEOUT))
+ .isTrue();
+
+ final UiSelector itemSelector = new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/icon_thumbnail");
+ final UiScrollable grid = new UiScrollable(gridList);
+ final int childCount = grid.getChildCount();
+ final int count = itemCount == -1 ? childCount : itemCount;
+
+ for (int i = 0; i < childCount; i++) {
+ final UiObject item = grid.getChildByInstance(itemSelector, i);
+ if (item.exists()) {
+ itemList.add(item);
+ }
+ if (itemList.size() == count) {
+ break;
+ }
+ }
+ return itemList;
+ }
+
+ public static UiObject findPreviewAddButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/preview_add_button"));
+ }
+
+ public static UiObject findPreviewAddOrSelectButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/preview_add_or_select_button"));
+ }
+
+ public static UiObject findAddButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/button_add"));
+ }
+
+ public static UiObject findProfileButton() {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/profile_button"));
+ }
+}
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
index 26ab62b..5e154f7 100644
--- a/tests/app/app/src/android/app/stubs/CommandReceiver.java
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -19,19 +19,15 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ForegroundServiceStartNotAllowedException;
-import android.app.IActivityManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
-import android.os.RemoteException;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -65,7 +61,6 @@
public static final int COMMAND_START_FOREGROUND_SERVICE_STICKY = 20;
public static final int COMMAND_STOP_FOREGROUND_SERVICE_STICKY = 21;
public static final int COMMAND_EMPTY = 22;
- public static final int COMMAND_START_FOREGROUND_SERVICE_SPOOF_PACKAGE_NAME = 23;
public static final int RESULT_CHILD_PROCESS_STARTED = IBinder.FIRST_CALL_TRANSACTION;
public static final int RESULT_CHILD_PROCESS_STOPPED = IBinder.FIRST_CALL_TRANSACTION + 1;
@@ -175,9 +170,6 @@
break;
case COMMAND_EMPTY:
break;
- case COMMAND_START_FOREGROUND_SERVICE_SPOOF_PACKAGE_NAME:
- doStartForegroundServiceSpoofPackageName(context, intent);
- break;
}
}
@@ -379,56 +371,6 @@
}).start();
}
- /**
- * Directly call IActivityManager.startService() using a spoofed packageName which is known to
- * be allowlisted by Android framework to be able to start foreground service
- * from the background. Framework will disallow the foreground service to start from the
- * background and a ForegroundServiceStartNotAllowedException will be caught.
- * @param context
- * @param commandIntent
- */
- private void doStartForegroundServiceSpoofPackageName(Context context, Intent commandIntent) {
- String targetPackage = getTargetPackage(commandIntent);
- Intent fgsIntent = new Intent();
- fgsIntent.putExtras(commandIntent);
- fgsIntent.setComponent(new ComponentName(targetPackage, FG_SERVICE_NAME));
- int command = LocalForegroundService.COMMAND_START_FOREGROUND;
- fgsIntent.putExtras(LocalForegroundService.newCommand(command));
- try {
- final PackageManager pm = context.getPackageManager();
- String spoofPackageName = pm.getAttentionServicePackageName();
- if (TextUtils.isEmpty(spoofPackageName)) {
- Log.d(TAG, "getAttentionServicePackageName() returns empty");
- spoofPackageName = pm.getSystemCaptionsServicePackageName();
- }
- if (TextUtils.isEmpty(spoofPackageName)) {
- Log.d(TAG, "getSystemCaptionsServicePackageName() returns empty");
- spoofPackageName = "android";
- }
- Log.d(TAG, "spoofPackageName: " + spoofPackageName);
- final IBinder activityProxy = android.os.ServiceManager.getService("activity");
- // Call IActivityManager.startService() directly using a spoofed packageName.
- IActivityManager.Stub.asInterface(activityProxy).startService(
- context.getIApplicationThread(),
- fgsIntent,
- null,
- true,
- spoofPackageName,
- null,
- android.os.Process.myUserHandle().getIdentifier()
- );
- } catch (ForegroundServiceStartNotAllowedException e) {
- Log.d(TAG, "startForegroundService gets an "
- + " ForegroundServiceStartNotAllowedException", e);
- } catch (LinkageError e) {
- // IActivityManager.startService() is a hidden API, access hidden API could get
- // LinkageError, consider the test as pass if we get LinkageError.
- Log.d(TAG, "startForegroundService gets an LinkageError", e);
- } catch (RemoteException e) {
- Log.d(TAG, "startForegroundService gets an RemoteException", e);
- }
- }
-
private String getTargetPackage(Intent intent) {
return intent.getStringExtra(EXTRA_TARGET_PACKAGE);
}
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 986eb63..d3e91c1 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -60,9 +60,6 @@
import android.platform.test.annotations.AsbSecurityTest;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.server.wm.settings.SettingsSession;
-import android.support.test.uiautomator.UiDevice;
-import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -1809,52 +1806,6 @@
}
/**
- * IActivityManager.startService() is called directly (does not go through
- * {@link Context#startForegroundService(Intent)}, a spoofed packageName "com.google.android.as"
- * is used as callingPackage. Although "com.google.android.as" is allowlisted to start
- * foreground service from the background, but framework will detect this is a spoofed
- * packageName and disallow foreground service start from the background.
- * @throws Exception
- */
- @Test
- public void testSpoofPackageName() throws Exception {
- ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
- PACKAGE_NAME_APP1, 0);
- WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
- WAITFOR_MSEC);
- // CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_SPOOF_PACKAGE_NAME needs access
- // to hidden API PackageManager.getAttentionServicePackageName() and
- // PackageManager.getSystemCaptionsServicePackageName(), so we need to call
- // hddenApiSettings.set("*") to exempt the hidden APIs.
- SettingsSession<String> hiddenApiSettings = new SettingsSession<>(
- Settings.Global.getUriFor(
- Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS),
- Settings.Global::getString, Settings.Global::putString);
- hiddenApiSettings.set("*");
- try {
- // Enable the FGS background startForeground() restriction.
- enableFgsRestriction(true, true, null);
- // Start FGS in BG state.
- WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
- waiter.prepare(ACTION_START_FGS_RESULT);
- CommandReceiver.sendCommand(mContext,
- CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_SPOOF_PACKAGE_NAME,
- PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
- // APP1 does not enter FGS state
- try {
- waiter.doWait(WAITFOR_MSEC);
- fail("Service should not enter foreground service state");
- } catch (Exception e) {
- }
- } finally {
- uid1Watcher.finish();
- if (hiddenApiSettings != null) {
- hiddenApiSettings.close();
- }
- }
- }
-
- /**
* Turn on the FGS BG-launch restriction. DeviceConfig can turn on restriction on the whole
* device (across all apps). AppCompat can turn on restriction on a single app package.
* @param enable true to turn on restriction, false to turn off.
diff --git a/tests/libcore/jsr166/Android.bp b/tests/libcore/jsr166/Android.bp
index 0ab6329..1addbfd 100644
--- a/tests/libcore/jsr166/Android.bp
+++ b/tests/libcore/jsr166/Android.bp
@@ -37,6 +37,6 @@
test_suites: [
"cts",
"general-tests",
- "mts",
+ "mts-art",
],
}
diff --git a/tests/libcore/jsr166/AndroidTest.xml b/tests/libcore/jsr166/AndroidTest.xml
index 93a2b76..3dc2bdb 100644
--- a/tests/libcore/jsr166/AndroidTest.xml
+++ b/tests/libcore/jsr166/AndroidTest.xml
@@ -54,4 +54,7 @@
<!-- ART Mainline Module (external (AOSP) version). -->
<option name="mainline-module-package-name" value="com.android.art" />
</object>
+
+ <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
</configuration>
diff --git a/tests/libcore/luni/AndroidTest.xml b/tests/libcore/luni/AndroidTest.xml
index 2173c92..8fcaaab 100644
--- a/tests/libcore/luni/AndroidTest.xml
+++ b/tests/libcore/luni/AndroidTest.xml
@@ -80,4 +80,7 @@
<!-- ART Mainline Module (external (AOSP) version). -->
<option name="mainline-module-package-name" value="com.android.art" />
</object>
+
+ <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
</configuration>
diff --git a/tests/libcore/ojluni/Android.bp b/tests/libcore/ojluni/Android.bp
index b11bc9b..1276621 100644
--- a/tests/libcore/ojluni/Android.bp
+++ b/tests/libcore/ojluni/Android.bp
@@ -41,6 +41,6 @@
test_suites: [
"cts",
"general-tests",
- "mts",
+ "mts-art",
],
}
diff --git a/tests/libcore/ojluni/AndroidTest.xml b/tests/libcore/ojluni/AndroidTest.xml
index 86e04f6..f929c4b 100644
--- a/tests/libcore/ojluni/AndroidTest.xml
+++ b/tests/libcore/ojluni/AndroidTest.xml
@@ -58,4 +58,7 @@
<!-- ART Mainline Module (external (AOSP) version). -->
<option name="mainline-module-package-name" value="com.android.art" />
</object>
+
+ <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
</configuration>
diff --git a/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml b/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml
index 8219e38c..c3e97a4 100644
--- a/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml
+++ b/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml
@@ -56,4 +56,7 @@
<!-- ART Mainline Module (external (AOSP) version). -->
<option name="mainline-module-package-name" value="com.android.art" />
</object>
+
+ <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
</configuration>
diff --git a/tests/libcore/runner/Android.bp b/tests/libcore/runner/Android.bp
index d54a198..4e0742f 100644
--- a/tests/libcore/runner/Android.bp
+++ b/tests/libcore/runner/Android.bp
@@ -31,6 +31,6 @@
test_suites: [
"cts",
"general-tests",
- "mts",
+ "mts-art",
],
}
diff --git a/tests/libcore/wycheproof-bc/AndroidTest.xml b/tests/libcore/wycheproof-bc/AndroidTest.xml
index b0471d0..dce14b5 100644
--- a/tests/libcore/wycheproof-bc/AndroidTest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidTest.xml
@@ -52,4 +52,7 @@
<!-- ART Mainline Module (external (AOSP) version). -->
<option name="mainline-module-package-name" value="com.android.art" />
</object>
+
+ <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
</configuration>
diff --git a/tests/media/Android.bp b/tests/media/Android.bp
index 81c56af..b2a2db3 100644
--- a/tests/media/Android.bp
+++ b/tests/media/Android.bp
@@ -36,6 +36,7 @@
"libctsmediav2muxer_jni",
"libctsmediav2extractor_jni",
"libctsmediav2codec_jni",
+ "libctsmediav2utils_jni",
],
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
diff --git a/tests/media/AndroidTest.xml b/tests/media/AndroidTest.xml
index 1ce5be4..02f2aca 100644
--- a/tests/media/AndroidTest.xml
+++ b/tests/media/AndroidTest.xml
@@ -26,7 +26,7 @@
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
<option name="push-all" value="true" />
- <option name="media-folder-name" value="CtsMediaV2TestCases-1.13" />
+ <option name="media-folder-name" value="CtsMediaV2TestCases-1.14" />
<option name="dynamic-config-module" value="CtsMediaV2TestCases" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/media/DynamicConfig.xml b/tests/media/DynamicConfig.xml
index ad4a006..572610a 100644
--- a/tests/media/DynamicConfig.xml
+++ b/tests/media/DynamicConfig.xml
@@ -1,5 +1,5 @@
<dynamicConfig>
<entry key="media_files_url">
- <value>https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.13.zip</value>
+ <value>https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.14.zip</value>
</entry>
</dynamicConfig>
diff --git a/tests/media/README.md b/tests/media/README.md
index 695ac32..db6eb116 100644
--- a/tests/media/README.md
+++ b/tests/media/README.md
@@ -3,7 +3,7 @@
The aim of these tests is not solely to verify the CDD requirements but also to test components, their plugins and their interactions with media framework.
-The test vectors used by the test suite is available at [link](https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.13.zip) and is downloaded automatically while running tests. Manual installation of these can be done using copy_media.sh script in this directory.
+The test vectors used by the test suite is available at [link](https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.14.zip) and is downloaded automatically while running tests. Manual installation of these can be done using copy_media.sh script in this directory.
The test suite looks to cover sdk/ndk api in normal and error scenarios. Error scenarios are separated from regular usage and are placed under class *UnitTest (MuxerUnitTest, ExtractorUnitTest, ...).
diff --git a/tests/media/copy_media.sh b/tests/media/copy_media.sh
index c89ea8f..0adef28 100755
--- a/tests/media/copy_media.sh
+++ b/tests/media/copy_media.sh
@@ -17,7 +17,7 @@
## script to install mediav2 test files manually
adbOptions=" "
-resLabel=CtsMediaV2TestCases-1.13
+resLabel=CtsMediaV2TestCases-1.14
srcDir="/tmp/$resLabel"
tgtDir="/sdcard/test"
usage="Usage: $0 [-h] [-s serial]"
diff --git a/tests/media/jni/Android.bp b/tests/media/jni/Android.bp
index d4e192c..95fb613 100644
--- a/tests/media/jni/Android.bp
+++ b/tests/media/jni/Android.bp
@@ -93,3 +93,25 @@
gtest: false,
sdk_version: "29",
}
+
+cc_test_library {
+ name: "libctsmediav2utils_jni",
+ srcs: [
+ "NativeMediaFormatUnitTest.cpp",
+ ],
+ shared_libs: [
+ "libmediandk",
+ "liblog",
+ ],
+ header_libs: ["liblog_headers"],
+ include_dirs: [
+ "frameworks/av/media/ndk/include/media",
+ ],
+ stl: "libc++_static",
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+ gtest: false,
+ sdk_version: "29",
+}
diff --git a/tests/media/jni/NativeMediaFormatUnitTest.cpp b/tests/media/jni/NativeMediaFormatUnitTest.cpp
new file mode 100644
index 0000000..e3241a3
--- /dev/null
+++ b/tests/media/jni/NativeMediaFormatUnitTest.cpp
@@ -0,0 +1,654 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeMediaFormatUnitTest"
+#include <log/log.h>
+#include <jni.h>
+#include <NdkMediaFormat.h>
+
+#include <cinttypes>
+#include <map>
+#include <string>
+
+static const char story[] = {"What if after you die, God asks you: 'so how was heaven'"};
+static const char dragon[] = {"e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6"};
+
+class Rect {
+public:
+ int left;
+ int top;
+ int right;
+ int bottom;
+
+ Rect(int a, int b, int c, int d) : left{a}, top{b}, right{c}, bottom{d} {};
+};
+
+class Buffer {
+public:
+ char* buffer;
+ size_t size;
+
+ explicit Buffer(char* buffer = nullptr, size_t size = 0) : buffer{buffer}, size{size} {};
+};
+
+class NativeMediaFormatUnitTest {
+private:
+ std::map<int32_t, const char*> mInt32KeyValuePairs;
+ std::map<int64_t, const char*> mInt64KeyValuePairs;
+ std::map<float, const char*> mFloatKeyValuePairs;
+ std::map<double, const char*> mDoubleKeyValuePairs;
+ std::map<size_t, const char*> mSizeKeyValuePairs;
+ std::map<const char*, const char*> mStringKeyValuePairs;
+ std::map<Rect*, const char*> mWindowKeyValuePairs;
+ std::map<Buffer*, const char*> mBufferKeyValuePairs;
+
+public:
+ NativeMediaFormatUnitTest();
+ ~NativeMediaFormatUnitTest();
+
+ bool validateFormatInt32(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+ bool validateFormatInt64(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+ bool validateFormatFloat(AMediaFormat* fmt, float offset = 0.0f, bool isClear = false);
+ bool validateFormatDouble(AMediaFormat* fmt, double offset = 0.0, bool isClear = false);
+ bool validateFormatSize(AMediaFormat* fmt, size_t offset = 0, bool isClear = false);
+ bool validateFormatString(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+ bool validateFormatRect(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+ bool validateFormatBuffer(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+ bool validateFormat(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+
+ void configureFormatInt32(AMediaFormat* fmt, int offset = 0);
+ void configureFormatInt64(AMediaFormat* fmt, int offset = 0);
+ void configureFormatFloat(AMediaFormat* fmt, float offset = 0.0f);
+ void configureFormatDouble(AMediaFormat* fmt, double offset = 0.0);
+ void configureFormatSize(AMediaFormat* fmt, size_t offset = 0);
+ void configureFormatString(AMediaFormat* fmt, int offset = 0);
+ void configureFormatRect(AMediaFormat* fmt, int offset = 0);
+ void configureFormatBuffer(AMediaFormat* fmt, int offset = 0);
+ void configureFormat(AMediaFormat* fmt, int offset = 0);
+};
+
+NativeMediaFormatUnitTest::NativeMediaFormatUnitTest() {
+ mInt32KeyValuePairs.insert({118, "elements in periodic table"});
+ mInt32KeyValuePairs.insert({5778, "surface temp. of sun in kelvin"});
+ mInt32KeyValuePairs.insert({8611, "k2 peak in mts"});
+ mInt32KeyValuePairs.insert({72, "heart rate in bpm"});
+ mInt64KeyValuePairs.insert({299792458L, "vel. of em wave in free space m/s"});
+ mInt64KeyValuePairs.insert({86400L, "number of seconds in a day"});
+ mInt64KeyValuePairs.insert({1520200000L, "distance of earth from the sun in km"});
+ mInt64KeyValuePairs.insert({39000000L, "forest area of the world km^2"});
+ mFloatKeyValuePairs.insert({22.0f / 7.0f, "pi"});
+ mFloatKeyValuePairs.insert({3.6f, "not great, not terrible"});
+ mFloatKeyValuePairs.insert({15.999f, "atomic weight of oxygen 8"});
+ mFloatKeyValuePairs.insert({2.7182f, "Euler's number"});
+ mDoubleKeyValuePairs.insert({44.0 / 7, "tau"});
+ mDoubleKeyValuePairs.insert({9.80665, "g on earth m/sec^2"});
+ mSizeKeyValuePairs.insert({sizeof(int64_t), "size of int64_t"});
+ mSizeKeyValuePairs.insert({sizeof(wchar_t), "size of wide char"});
+ mSizeKeyValuePairs.insert({sizeof(intptr_t), "size of pointer variable"});
+ mSizeKeyValuePairs.insert({sizeof *this, "size of class NativeMediaFormatUnitTest"});
+ mStringKeyValuePairs.insert(
+ {"Discovered radium and polonium, and made huge contribution to finding treatments "
+ "for cancer", "Marie Curie"});
+ mStringKeyValuePairs.insert({"Sun rises in the east has zero entropy", "Shannon"});
+ mWindowKeyValuePairs.insert({new Rect{12, 15, 12, 21}, "trapezoid"});
+ mWindowKeyValuePairs.insert({new Rect{12, 12, 12, 12}, "rhombus"});
+ mWindowKeyValuePairs.insert({new Rect{12, 15, 12, 15}, "rectangle"});
+ mWindowKeyValuePairs.insert({new Rect{12, 15, 18, 21}, "quadrilateral"});
+ mBufferKeyValuePairs.insert({new Buffer(), "empty buffer"});
+ size_t sz = strlen(story) + 1;
+ auto* quote = new Buffer{new char[sz], sz};
+ memcpy(quote->buffer, story, sz);
+ mBufferKeyValuePairs.insert({quote, "one line story"});
+ sz = strlen(dragon) + 1;
+ auto* chess = new Buffer(new char[sz], sz);
+ memcpy(chess->buffer, dragon, sz);
+ mBufferKeyValuePairs.insert({chess, "sicilian dragon"});
+}
+
+NativeMediaFormatUnitTest::~NativeMediaFormatUnitTest() {
+ for (auto it : mWindowKeyValuePairs) {
+ delete it.first;
+ }
+ for (auto it : mBufferKeyValuePairs) {
+ delete[] it.first->buffer;
+ delete it.first;
+ }
+}
+
+bool NativeMediaFormatUnitTest::validateFormatInt32(AMediaFormat* fmt, int offset, bool isClear) {
+ bool status = true;
+ int32_t val;
+ const char* toString = AMediaFormat_toString(fmt);
+ for (auto it : mInt32KeyValuePairs) {
+ bool result = AMediaFormat_getInt32(fmt, it.second, &val);
+ if (isClear) {
+ if (result) {
+ ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+ status &= false;
+ }
+ } else {
+ if (!result) {
+ ALOGE("MediaFormat doesn't contain key %s", it.second);
+ status &= false;
+ } else if (val != it.first + offset) {
+ ALOGE("MediaFormat Value for Key %s is not %d but %d", it.second, it.first + offset,
+ val);
+ status &= false;
+ }
+ if (strstr(toString, it.second) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+ status &= false;
+ }
+ if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+ std::to_string(it.first + offset).c_str());
+ status &= false;
+ }
+ }
+ }
+ if (AMediaFormat_getInt32(fmt, "hello world", &val)) {
+ ALOGE("MediaFormat has value for key 'hello world' ");
+ status &= false;
+ }
+ return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatInt64(AMediaFormat* fmt, int offset, bool isClear) {
+ bool status = true;
+ int64_t val;
+ const char* toString = AMediaFormat_toString(fmt);
+ for (auto it : mInt64KeyValuePairs) {
+ bool result = AMediaFormat_getInt64(fmt, it.second, &val);
+ if (isClear) {
+ if (result) {
+ ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+ status &= false;
+ }
+ } else {
+ if (!result) {
+ ALOGE("MediaFormat doesn't contain key %s", it.second);
+ status &= false;
+ } else if (val != it.first + offset) {
+ ALOGE("MediaFormat Value for Key %s is not %" PRId64 "but %" PRId64, it.second,
+ it.first + offset, val);
+ status &= false;
+ }
+ if (strstr(toString, it.second) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+ status &= false;
+ }
+ if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+ std::to_string(it.first + offset).c_str());
+ status &= false;
+ }
+ }
+ }
+ if (AMediaFormat_getInt64(fmt, "hello world", &val)) {
+ ALOGE("MediaFormat has value for key 'hello world' ");
+ status &= false;
+ }
+ return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatFloat(AMediaFormat* fmt, float offset, bool isClear) {
+ bool status = true;
+ float val;
+ const char* toString = AMediaFormat_toString(fmt);
+ for (auto it : mFloatKeyValuePairs) {
+ bool result = AMediaFormat_getFloat(fmt, it.second, &val);
+ if (isClear) {
+ if (result) {
+ ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+ status &= false;
+ }
+ } else {
+ if (!result) {
+ ALOGE("MediaFormat doesn't contain key %s", it.second);
+ status &= false;
+ } else if (val != it.first + offset) {
+ ALOGE("MediaFormat Value for Key %s is not %f but %f", it.second, it.first + offset,
+ val);
+ status &= false;
+ }
+ if (strstr(toString, it.second) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+ status &= false;
+ }
+ if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+ std::to_string(it.first + offset).c_str());
+ status &= false;
+ }
+ }
+ }
+ if (AMediaFormat_getFloat(fmt, "hello world", &val)) {
+ ALOGE("MediaFormat has value for key 'hello world' ");
+ status &= false;
+ }
+ return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatDouble(AMediaFormat* fmt, double offset,
+ bool isClear) {
+ bool status = true;
+ double val;
+ const char* toString = AMediaFormat_toString(fmt);
+ for (auto it : mDoubleKeyValuePairs) {
+ bool result = AMediaFormat_getDouble(fmt, it.second, &val);
+ if (isClear) {
+ if (result) {
+ ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+ status &= false;
+ }
+ } else {
+ if (!result) {
+ ALOGE("MediaFormat doesn't contain key %s", it.second);
+ status &= false;
+ } else if (val != it.first + offset) {
+ ALOGE("MediaFormat Value for Key %s is not %f but %f", it.second, it.first + offset,
+ val);
+ status &= false;
+ }
+ if (strstr(toString, it.second) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+ status &= false;
+ }
+ if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+ std::to_string(it.first + offset).c_str());
+ status &= false;
+ }
+ }
+ }
+ if (AMediaFormat_getDouble(fmt, "hello world", &val)) {
+ ALOGE("MediaFormat has value for key 'hello world' ");
+ status &= false;
+ }
+ return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatSize(AMediaFormat* fmt, size_t offset, bool isClear) {
+ bool status = true;
+ size_t val;
+ const char* toString = AMediaFormat_toString(fmt);
+ for (auto it : mSizeKeyValuePairs) {
+ bool result = AMediaFormat_getSize(fmt, it.second, &val);
+ if (isClear) {
+ if (result) {
+ ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+ status &= false;
+ }
+ } else {
+ if (!result) {
+ ALOGE("MediaFormat doesn't contain key %s", it.second);
+ status &= false;
+ } else if (val != it.first + offset) {
+ ALOGE("MediaFormat Value for Key %s is not %zu but %zu", it.second,
+ it.first + offset, val);
+ status &= false;
+ }
+ if (strstr(toString, it.second) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+ status &= false;
+ }
+ if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+ std::to_string(it.first + offset).c_str());
+ status &= false;
+ }
+ }
+ }
+ if (AMediaFormat_getSize(fmt, "hello world", &val)) {
+ ALOGE("MediaFormat has value for key 'hello world' ");
+ status &= false;
+ }
+ return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatString(AMediaFormat* fmt, int offset, bool isClear) {
+ bool status = true;
+ const char* val;
+ const char* toString = AMediaFormat_toString(fmt);
+ for (auto it : mStringKeyValuePairs) {
+ bool result = AMediaFormat_getString(fmt, it.second, &val);
+ if (isClear) {
+ if (result) {
+ ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+ status &= false;
+ }
+ } else {
+ std::string s = it.first + std::to_string(offset);
+ if (!result) {
+ ALOGE("MediaFormat doesn't contain key %s", it.second);
+ status &= false;
+ } else if (s != val) {
+ ALOGE("MediaFormat Value for Key %s is not %s but %s", it.second, s.c_str(), val);
+ status &= false;
+ }
+ if (strstr(toString, it.second) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+ status &= false;
+ }
+ if (strstr(toString, s.c_str()) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, s.c_str());
+ status &= false;
+ }
+ }
+ }
+ if (AMediaFormat_getString(fmt, "hello world", &val)) {
+ ALOGE("MediaFormat has value for key 'hello world' ");
+ status &= false;
+ }
+ return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatRect(AMediaFormat* fmt, int offset, bool isClear) {
+ bool status = true;
+ int left, top, right, bottom;
+ const char* toString = AMediaFormat_toString(fmt);
+ for (auto it : mWindowKeyValuePairs) {
+ bool result = AMediaFormat_getRect(fmt, it.second, &left, &top, &right, &bottom);
+ if (isClear) {
+ if (result) {
+ ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+ status &= false;
+ }
+ } else {
+ if (!result) {
+ ALOGE("MediaFormat doesn't contain key %s", it.second);
+ status &= false;
+ } else if (left != it.first->left + offset || top != it.first->top + offset ||
+ right != it.first->right + offset || bottom != it.first->bottom + offset) {
+ ALOGE("MediaFormat Value for Key %s is not (%d, %d, %d, %d)) but (%d, %d, %d, %d)",
+ it.second, it.first->left, it.first->top, it.first->right, it.first->bottom,
+ left, top, right, bottom);
+ status &= false;
+ }
+ if (strstr(toString, it.second) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+ status &= false;
+ }
+ if (strstr(toString, std::to_string(it.first->left + offset).c_str()) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+ std::to_string(it.first->left + offset).c_str());
+ status &= false;
+ }
+ if (strstr(toString, std::to_string(it.first->top + offset).c_str()) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+ std::to_string(it.first->top + offset).c_str());
+ status &= false;
+ }
+ if (strstr(toString, std::to_string(it.first->right + offset).c_str()) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+ std::to_string(it.first->right + offset).c_str());
+ status &= false;
+ }
+ if (strstr(toString, std::to_string(it.first->bottom + offset).c_str()) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+ std::to_string(it.first->bottom + offset).c_str());
+ status &= false;
+ }
+ }
+ }
+ if (AMediaFormat_getRect(fmt, "hello world", &left, &top, &right, &bottom)) {
+ ALOGE("MediaFormat has value for key 'hello world' ");
+ status &= false;
+ }
+ return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatBuffer(AMediaFormat* fmt, int offset, bool isClear) {
+ bool status = true;
+ void* data;
+ size_t size;
+ const char* toString = AMediaFormat_toString(fmt);
+ for (auto it : mBufferKeyValuePairs) {
+ bool result = AMediaFormat_getBuffer(fmt, it.second, &data, &size);
+ if (isClear) {
+ if (result) {
+ ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+ status &= false;
+ }
+ } else {
+ if (!result) {
+ ALOGE("MediaFormat doesn't contain key %s", it.second);
+ status &= false;
+ } else if (size != (offset == 0 ? it.first->size : it.first->size / 2)) {
+ ALOGE("MediaFormat Value for Key %s is not %zu but %zu", it.second,
+ (offset == 0 ? it.first->size : it.first->size / 2), size);
+ status &= false;
+ } else {
+ if (it.first->buffer != nullptr &&
+ memcmp(data, it.first->buffer + it.first->size - size, size) != 0) {
+ ALOGE("MediaFormat Value for Key %s is not %s but %s {%zu}", it.second,
+ it.first->buffer + it.first->size - size, (char*)data, size);
+ status &= false;
+ }
+ }
+ if (strstr(toString, it.second) == nullptr) {
+ ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+ status &= false;
+ }
+ }
+ }
+ if (AMediaFormat_getBuffer(fmt, "hello world", &data, &size)) {
+ ALOGE("MediaFormat has value for key 'hello world' ");
+ status &= false;
+ }
+ return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormat(AMediaFormat* fmt, int offset, bool isClear) {
+ bool status = validateFormatInt32(fmt, offset, isClear);
+ status &= validateFormatInt64(fmt, offset, isClear);
+ status &= validateFormatFloat(fmt, offset, isClear);
+ status &= validateFormatDouble(fmt, offset, isClear);
+ status &= validateFormatSize(fmt, offset, isClear);
+ status &= validateFormatString(fmt, offset, isClear);
+ status &= validateFormatRect(fmt, offset, isClear);
+ status &= validateFormatBuffer(fmt, offset, isClear);
+ return status;
+}
+
+void NativeMediaFormatUnitTest::configureFormatInt32(AMediaFormat* fmt, int offset) {
+ for (auto it : mInt32KeyValuePairs) {
+ AMediaFormat_setInt32(fmt, it.second, it.first + offset);
+ }
+}
+
+void NativeMediaFormatUnitTest::configureFormatInt64(AMediaFormat* fmt, int offset) {
+ for (auto it : mInt64KeyValuePairs) {
+ AMediaFormat_setInt64(fmt, it.second, it.first + offset);
+ }
+}
+
+void NativeMediaFormatUnitTest::configureFormatFloat(AMediaFormat* fmt, float offset) {
+ for (auto it : mFloatKeyValuePairs) {
+ AMediaFormat_setFloat(fmt, it.second, it.first + offset);
+ }
+}
+
+void NativeMediaFormatUnitTest::configureFormatDouble(AMediaFormat* fmt, double offset) {
+ for (auto it : mDoubleKeyValuePairs) {
+ AMediaFormat_setDouble(fmt, it.second, it.first + offset);
+ }
+}
+
+void NativeMediaFormatUnitTest::configureFormatSize(AMediaFormat* fmt, size_t offset) {
+ for (auto it : mSizeKeyValuePairs) {
+ AMediaFormat_setSize(fmt, it.second, it.first + offset);
+ }
+}
+
+void NativeMediaFormatUnitTest::configureFormatString(AMediaFormat* fmt, int offset) {
+ for (auto it : mStringKeyValuePairs) {
+ std::string s1 = it.first + std::to_string(offset);
+ AMediaFormat_setString(fmt, it.second, s1.c_str());
+ }
+}
+
+void NativeMediaFormatUnitTest::configureFormatRect(AMediaFormat* fmt, int offset) {
+ for (auto it : mWindowKeyValuePairs) {
+ AMediaFormat_setRect(fmt, it.second, it.first->left + offset, it.first->top + offset,
+ it.first->right + offset, it.first->bottom + offset);
+ }
+}
+
+void NativeMediaFormatUnitTest::configureFormatBuffer(AMediaFormat* fmt, int offset) {
+ for (auto it : mBufferKeyValuePairs) {
+ int sz = offset == 0 ? it.first->size : it.first->size / 2;
+ AMediaFormat_setBuffer(fmt, it.second, it.first->buffer + it.first->size - sz, sz);
+ }
+}
+
+void NativeMediaFormatUnitTest::configureFormat(AMediaFormat* fmt, int offset) {
+ configureFormatInt32(fmt, offset);
+ configureFormatInt64(fmt, offset);
+ configureFormatFloat(fmt, offset);
+ configureFormatDouble(fmt, offset);
+ configureFormatSize(fmt, offset);
+ configureFormatString(fmt, offset);
+ configureFormatRect(fmt, offset);
+ configureFormatBuffer(fmt, offset);
+}
+
+// 1. configure format with default values and validate the same
+// 2. copy configured format to an empty format and validate the copied format
+// 3. overwrite copied format with default + offset values and validate the updated format
+// 4. overwrite updated format with default values using AMediaFormat_copy API and validate the same
+// 5. clear mediaformat and validate if keys are not present
+static bool testMediaFormatAllNative() {
+ auto* nmf = new NativeMediaFormatUnitTest();
+ AMediaFormat* fmtOrig = AMediaFormat_new();
+ AMediaFormat* fmtDup = AMediaFormat_new();
+ const int offset = 123;
+
+ nmf->configureFormat(fmtOrig);
+ bool status = nmf->validateFormat(fmtOrig);
+
+ AMediaFormat_copy(fmtDup, fmtOrig);
+ status &= nmf->validateFormat(fmtDup);
+
+ nmf->configureFormat(fmtDup, offset);
+ status &= nmf->validateFormat(fmtDup, offset);
+
+ AMediaFormat_copy(fmtDup, fmtOrig);
+ status &= nmf->validateFormat(fmtDup);
+
+ AMediaFormat_clear(fmtDup);
+ status &= nmf->validateFormat(fmtDup, offset, true);
+
+ AMediaFormat_delete(fmtOrig);
+ AMediaFormat_delete(fmtDup);
+ delete nmf;
+
+ return status;
+}
+
+// 1. configure format with default values and validate the same
+// 2. copy configured format to an empty format and validate the copied format
+// 3. overwrite copied format with default + offset values and validate the updated format
+// 4. overwrite updated format with default values using AMediaFormat_copy API and validate the same
+#define testMediaFormatfuncNative(func) \
+ static bool testMediaFormat##func##Native() { \
+ auto* nmf = new NativeMediaFormatUnitTest(); \
+ AMediaFormat* fmtOrig = AMediaFormat_new(); \
+ AMediaFormat* fmtDup = AMediaFormat_new(); \
+ const int offset = 12345; \
+ \
+ nmf->configureFormat##func(fmtOrig); \
+ bool status = nmf->validateFormat##func(fmtOrig); \
+ \
+ AMediaFormat_copy(fmtDup, fmtOrig); \
+ status &= nmf->validateFormat##func(fmtDup); \
+ \
+ nmf->configureFormat##func(fmtDup, offset); \
+ status &= nmf->validateFormat##func(fmtDup, offset); \
+ \
+ AMediaFormat_copy(fmtDup, fmtOrig); \
+ status &= nmf->validateFormat##func(fmtDup); \
+ \
+ AMediaFormat_clear(fmtDup); \
+ status &= nmf->validateFormat##func(fmtDup, offset, true); \
+ AMediaFormat_delete(fmtOrig); \
+ AMediaFormat_delete(fmtDup); \
+ delete nmf; \
+ return status; \
+ }
+
+testMediaFormatfuncNative(Int32)
+
+testMediaFormatfuncNative(Int64)
+
+testMediaFormatfuncNative(Float)
+
+testMediaFormatfuncNative(Double)
+
+testMediaFormatfuncNative(Size)
+
+testMediaFormatfuncNative(String)
+
+testMediaFormatfuncNative(Rect)
+
+testMediaFormatfuncNative(Buffer)
+
+#define nativeTestMediaFormatfunc(func) \
+ static jboolean nativeTestMediaFormat##func(JNIEnv*, jobject) { \
+ return static_cast<jboolean>(testMediaFormat##func##Native()); \
+ }
+
+nativeTestMediaFormatfunc(Int32)
+
+nativeTestMediaFormatfunc(Int64)
+
+nativeTestMediaFormatfunc(Float)
+
+nativeTestMediaFormatfunc(Double)
+
+nativeTestMediaFormatfunc(Size)
+
+nativeTestMediaFormatfunc(String)
+
+nativeTestMediaFormatfunc(Rect)
+
+nativeTestMediaFormatfunc(Buffer)
+
+nativeTestMediaFormatfunc(All)
+
+int registerAndroidMediaV2CtsMediaFormatUnitTest(JNIEnv* env) {
+ const JNINativeMethod methodTable[] = {
+ {"nativeTestMediaFormatInt32", "()Z", (void*)nativeTestMediaFormatInt32},
+ {"nativeTestMediaFormatInt64", "()Z", (void*)nativeTestMediaFormatInt64},
+ {"nativeTestMediaFormatFloat", "()Z", (void*)nativeTestMediaFormatFloat},
+ {"nativeTestMediaFormatDouble", "()Z", (void*)nativeTestMediaFormatDouble},
+ {"nativeTestMediaFormatSize", "()Z", (void*)nativeTestMediaFormatSize},
+ {"nativeTestMediaFormatString", "()Z", (void*)nativeTestMediaFormatString},
+ {"nativeTestMediaFormatRect", "()Z", (void*)nativeTestMediaFormatRect},
+ {"nativeTestMediaFormatBuffer", "()Z", (void*)nativeTestMediaFormatBuffer},
+ {"nativeTestMediaFormatAll", "()Z", (void*)nativeTestMediaFormatAll},
+ };
+ jclass c = env->FindClass("android/mediav2/cts/MediaFormatUnitTest");
+ return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
+
+extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
+ if (registerAndroidMediaV2CtsMediaFormatUnitTest(env) != JNI_OK) return JNI_ERR;
+ return JNI_VERSION_1_6;
+}
diff --git a/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java b/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java
index e803863..ae9054f 100644
--- a/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java
+++ b/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java
@@ -219,6 +219,7 @@
queueEOS();
waitForAllOutputs();
mCodec.reset();
+ mCodec.release();
}
tearDownSurface();
}
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
index 832127d..457ebf7 100644
--- a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
@@ -706,7 +706,6 @@
private native boolean nativeTestSetForceSyncFrame(String encoder, String file, String mime,
int[] list0, int[] list1, int[] list2, int colorFormat);
- @Ignore("TODO(b/) = test sometimes timesout")
@LargeTest
@Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
public void testSetForceSyncFrameNative() throws IOException {
@@ -742,8 +741,6 @@
mOutputBuff = new OutputManager();
mSaveToMem = true;
{
- /* TODO(b/147574800) */
- if (mCodecName.equals("c2.android.hevc.encoder")) return;
mCodec = MediaCodec.createByCodecName(mCodecName);
format.removeKey(MediaFormat.KEY_BITRATE_MODE);
MediaCodecInfo.EncoderCapabilities cap =
@@ -803,7 +800,6 @@
private native boolean nativeTestAdaptiveBitRate(String encoder, String file, String mime,
int[] list0, int[] list1, int[] list2, int colorFormat);
- @Ignore("TODO(b/) = test sometimes timesout")
@LargeTest
@Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
public void testAdaptiveBitRateNative() throws IOException {
@@ -811,8 +807,6 @@
mAdaptiveBitrateMimeList.contains(mMime));
int colorFormat = -1;
{
- /* TODO(b/147574800) */
- if (mCodecName.equals("c2.android.hevc.encoder")) return;
if (!mIsAudio) {
colorFormat = findByteBufferColorFormat(mCodecName, mMime);
assertTrue("no valid color formats received", colorFormat != -1);
diff --git a/tests/media/src/android/mediav2/cts/CodecTestBase.java b/tests/media/src/android/mediav2/cts/CodecTestBase.java
index ea542d6..5269cec 100644
--- a/tests/media/src/android/mediav2/cts/CodecTestBase.java
+++ b/tests/media/src/android/mediav2/cts/CodecTestBase.java
@@ -16,9 +16,11 @@
package android.mediav2.cts;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.media.Image;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
@@ -29,6 +31,7 @@
import android.os.PersistableBundle;
import android.util.Log;
import android.util.Pair;
+import android.view.Display;
import android.view.Surface;
import androidx.annotation.NonNull;
@@ -60,6 +63,7 @@
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+import static android.media.MediaCodecInfo.CodecProfileLevel.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -524,8 +528,8 @@
static final int RETRY_LIMIT = 100; // max poll counter before test aborts and returns error
static final String INVALID_CODEC = "unknown.codec_";
static final String mInpPrefix = WorkDir.getMediaDirString();
- static final PackageManager pm =
- InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+ static final Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ static final PackageManager pm = mContext.getPackageManager();
static String mimeSelKeys;
static String codecPrefix;
@@ -629,6 +633,47 @@
return isSupported;
}
+ static boolean doesAnyFormatHaveHDRProfile(String mime, ArrayList<MediaFormat> formats) {
+ boolean isHDR = false;
+ for (MediaFormat format : formats) {
+ assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
+ if (mime.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+ int profile = format.getInteger(MediaFormat.KEY_PROFILE);
+ if (profile == AVCProfileHigh10 || profile == AVCProfileHigh422 ||
+ profile == AVCProfileHigh444) {
+ isHDR = true;
+ break;
+ }
+ } else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+ int profile = format.getInteger(MediaFormat.KEY_PROFILE, VP9Profile0);
+ if (profile == VP9Profile2HDR || profile == VP9Profile3HDR ||
+ profile == VP9Profile2HDR10Plus || profile == VP9Profile3HDR10Plus) {
+ isHDR = true;
+ break;
+ }
+ } else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+ int profile = format.getInteger(MediaFormat.KEY_PROFILE, HEVCProfileMain);
+ if (profile == HEVCProfileMain10HDR10 || profile == HEVCProfileMain10HDR10Plus) {
+ isHDR = true;
+ break;
+ }
+ } else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_AV1)) {
+ int profile = format.getInteger(MediaFormat.KEY_PROFILE, AV1ProfileMain8);
+ if (profile == AV1ProfileMain10HDR10 || profile == AV1ProfileMain10HDR10Plus) {
+ isHDR = true;
+ break;
+ }
+ }
+ }
+ return isHDR;
+ }
+
+ static boolean canDisplaySupportHDRContent() {
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ return displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
+ .getSupportedHdrTypes().length != 0;
+ }
+
static boolean areFormatsSupported(String name, String mime, ArrayList<MediaFormat> formats)
throws IOException {
MediaCodec codec = MediaCodec.createByCodecName(name);
@@ -1133,6 +1178,7 @@
MediaFormat setUpSource(String prefix, String srcFile) throws IOException {
mExtractor = new MediaExtractor();
+ Preconditions.assertTestFileExists(prefix + srcFile);
mExtractor.setDataSource(prefix + srcFile);
for (int trackID = 0; trackID < mExtractor.getTrackCount(); trackID++) {
MediaFormat format = mExtractor.getTrackFormat(trackID);
diff --git a/tests/media/src/android/mediav2/cts/CodecUnitTest.java b/tests/media/src/android/mediav2/cts/CodecUnitTest.java
index 3901b88..803b144 100644
--- a/tests/media/src/android/mediav2/cts/CodecUnitTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecUnitTest.java
@@ -23,6 +23,7 @@
import android.os.Bundle;
import android.util.Pair;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.After;
@@ -49,6 +50,10 @@
static final long STALL_TIME_MS = 1000;
@SmallTest
+ // Following tests were added in Android R and are not limited to c2.android.* codecs.
+ // Hence limit the tests to Android R and above and also annotate as NonMediaMainlineTest
+ @SdkSuppress(minSdkVersion = 30)
+ @NonMediaMainlineTest
public static class TestApi extends CodecTestBase {
@Rule
public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -1964,6 +1969,10 @@
}
@SmallTest
+ // Following tests were added in Android R and are not limited to c2.android.* codecs.
+ // Hence limit the tests to Android R and above and also annotate as NonMediaMainlineTest
+ @SdkSuppress(minSdkVersion = 30)
+ @NonMediaMainlineTest
public static class TestApiNative {
@Rule
public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java b/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java
index ce36bd3..15f5e65 100644
--- a/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java
+++ b/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java
@@ -51,10 +51,10 @@
mCheckESList = new ArrayList<>();
mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AVC);
mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
- /* TODO (b/165492703) Mpeg2 and (b/165787556) AV1 has problems in signalling color
+ /* TODO (b/165492703) Mpeg2 has problems in signalling color
aspects information via elementary stream. */
// mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_MPEG2);
- // mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AV1);
+ mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AV1);
mCanIgnoreColorBox = canIgnoreColorBox;
}
@@ -246,6 +246,9 @@
ArrayList<MediaFormat> formats = new ArrayList<>();
formats.add(format);
Assume.assumeTrue(areFormatsSupported(mCodecName, mMime, formats));
+ if (doesAnyFormatHaveHDRProfile(mMime, formats)) {
+ Assume.assumeTrue(canDisplaySupportHDRContent());
+ }
CodecTestActivity activity = mActivityRule.getActivity();
setUpSurface(activity);
activity.setScreenParams(getWidth(format), getHeight(format), true);
diff --git a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
index f24a4b1..51a10fc 100644
--- a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
+++ b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
@@ -155,8 +155,7 @@
{MediaFormat.MIMETYPE_VIDEO_H263, 16384000, 720, 480, 60},
{MediaFormat.MIMETYPE_VIDEO_H263, 16384000, 720, 576, 50},
- // TODO (b/151429828)
- //{MediaFormat.MIMETYPE_VIDEO_HEVC, 128000, 176, 144, 15},
+ {MediaFormat.MIMETYPE_VIDEO_HEVC, 128000, 176, 144, 15},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 1500000, 352, 288, 30},
// TODO (b/152576008) - Limit HEVC Encoder test to 512x512
{MediaFormat.MIMETYPE_VIDEO_HEVC, 3000000, 512, 512, 30},
diff --git a/tests/media/src/android/mediav2/cts/ExtractorTest.java b/tests/media/src/android/mediav2/cts/ExtractorTest.java
index b7840db..1964fe8 100644
--- a/tests/media/src/android/mediav2/cts/ExtractorTest.java
+++ b/tests/media/src/android/mediav2/cts/ExtractorTest.java
@@ -1603,6 +1603,7 @@
for (String file : mInpFiles) {
MediaFormat format = null;
MediaExtractor extractor = new MediaExtractor();
+ Preconditions.assertTestFileExists(mInpPrefix + file);
extractor.setDataSource(mInpPrefix + file);
for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) {
MediaFormat fmt = extractor.getTrackFormat(trackID);
@@ -1740,6 +1741,7 @@
strTok.parseNumbers();
MediaExtractor extractor = new MediaExtractor();
+ Preconditions.assertTestFileExists(mInpPrefix + mRefFile);
extractor.setDataSource(mInpPrefix + mRefFile);
assertTrue(mTrackIndex < extractor.getTrackCount());
extractor.selectTrack(mTrackIndex);
diff --git a/tests/media/src/android/mediav2/cts/MediaFormatUnitTest.java b/tests/media/src/android/mediav2/cts/MediaFormatUnitTest.java
new file mode 100644
index 0000000..6047f30
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/MediaFormatUnitTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediav2.cts;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MediaFormatUnitTest {
+ static final int PER_TEST_TIMEOUT_MS = 10000;
+
+ static {
+ System.loadLibrary("ctsmediav2utils_jni");
+ }
+
+ @Rule
+ public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+ @Test
+ public void testMediaFormatNativeInt32() {
+ assertTrue(nativeTestMediaFormatInt32());
+ }
+
+ private native boolean nativeTestMediaFormatInt32();
+
+ @Test
+ public void testMediaFormatNativeInt64() {
+ assertTrue(nativeTestMediaFormatInt64());
+ }
+
+ private native boolean nativeTestMediaFormatInt64();
+
+ @Test
+ public void testMediaFormatNativeFloat() {
+ assertTrue(nativeTestMediaFormatFloat());
+ }
+
+ private native boolean nativeTestMediaFormatFloat();
+
+ @Test
+ public void testMediaFormatNativeDouble() {
+ assertTrue(nativeTestMediaFormatDouble());
+ }
+
+ private native boolean nativeTestMediaFormatDouble();
+
+ @Test
+ public void testMediaFormatNativeSize() {
+ assertTrue(nativeTestMediaFormatSize());
+ }
+
+ private native boolean nativeTestMediaFormatSize();
+
+ @Test
+ public void testMediaFormatNativeString() {
+ assertTrue(nativeTestMediaFormatString());
+ }
+
+ private native boolean nativeTestMediaFormatString();
+
+ @Test
+ public void testMediaFormatNativeRect() {
+ assertTrue(nativeTestMediaFormatRect());
+ }
+
+ private native boolean nativeTestMediaFormatRect();
+
+ @Test
+ public void testMediaFormatNativeBuffer() {
+ assertTrue(nativeTestMediaFormatBuffer());
+ }
+
+ private native boolean nativeTestMediaFormatBuffer();
+
+ @Test
+ public void testMediaFormatNativeAll() {
+ assertTrue(nativeTestMediaFormatAll());
+ }
+
+ private native boolean nativeTestMediaFormatAll();
+}
diff --git a/tests/media/src/android/mediav2/cts/WorkDir.java b/tests/media/src/android/mediav2/cts/WorkDir.java
index 9490d69..698eb6b 100644
--- a/tests/media/src/android/mediav2/cts/WorkDir.java
+++ b/tests/media/src/android/mediav2/cts/WorkDir.java
@@ -40,7 +40,7 @@
// user has specified the mediaDirString via instrumentation-arg
return mediaDirString + ((mediaDirString.endsWith("/")) ? "" : "/");
} else {
- return (getTopDirString() + "test/CtsMediaV2TestCases-1.13/");
+ return (getTopDirString() + "test/CtsMediaV2TestCases-1.14/");
}
}
}
diff --git a/tests/tests/netpermission/updatestatspermission/Android.bp b/tests/net/Android.bp
similarity index 68%
copy from tests/tests/netpermission/updatestatspermission/Android.bp
copy to tests/net/Android.bp
index 7a24886..2a4153a 100644
--- a/tests/tests/netpermission/updatestatspermission/Android.bp
+++ b/tests/net/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,18 +16,9 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test {
- name: "CtsNetTestCasesUpdateStatsPermission",
- defaults: ["cts_defaults"],
-
+java_library {
+ name: "CtsNetTestsNonUpdatableLib",
srcs: ["src/**/*.java"],
-
- static_libs: ["ctstestrunner-axt"],
-
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- ],
-
+ static_libs: ["androidx.test.rules"],
+ platform_apis: true,
}
diff --git a/tests/net/OWNERS b/tests/net/OWNERS
new file mode 100644
index 0000000..67e4fc9
--- /dev/null
+++ b/tests/net/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 31808
+set noparent
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
\ No newline at end of file
diff --git a/tests/net/TEST_MAPPING b/tests/net/TEST_MAPPING
new file mode 100644
index 0000000..a6a02d5
--- /dev/null
+++ b/tests/net/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "packages/modules/Connectivity"
+ }
+ ]
+}
diff --git a/tests/net/src/android/net/cts/LocalSocketTest.java b/tests/net/src/android/net/cts/LocalSocketTest.java
new file mode 100644
index 0000000..969f706
--- /dev/null
+++ b/tests/net/src/android/net/cts/LocalSocketTest.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import android.net.Credentials;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.ParcelFileDescriptor;
+import android.system.Os;
+import android.system.OsConstants;
+
+import junit.framework.TestCase;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+public class LocalSocketTest extends TestCase {
+ private final static String ADDRESS_PREFIX = "com.android.net.LocalSocketTest";
+
+ public void testLocalConnections() throws IOException {
+ String address = ADDRESS_PREFIX + "_testLocalConnections";
+ // create client and server socket
+ LocalServerSocket localServerSocket = new LocalServerSocket(address);
+ LocalSocket clientSocket = new LocalSocket();
+
+ // establish connection between client and server
+ LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
+ assertFalse(clientSocket.isConnected());
+ clientSocket.connect(locSockAddr);
+ assertTrue(clientSocket.isConnected());
+
+ LocalSocket serverSocket = localServerSocket.accept();
+ assertTrue(serverSocket.isConnected());
+ assertTrue(serverSocket.isBound());
+ try {
+ serverSocket.bind(localServerSocket.getLocalSocketAddress());
+ fail("Cannot bind a LocalSocket from accept()");
+ } catch (IOException expected) {
+ }
+ try {
+ serverSocket.connect(locSockAddr);
+ fail("Cannot connect a LocalSocket from accept()");
+ } catch (IOException expected) {
+ }
+
+ Credentials credent = clientSocket.getPeerCredentials();
+ assertTrue(0 != credent.getPid());
+
+ // send data from client to server
+ OutputStream clientOutStream = clientSocket.getOutputStream();
+ clientOutStream.write(12);
+ InputStream serverInStream = serverSocket.getInputStream();
+ assertEquals(12, serverInStream.read());
+
+ //send data from server to client
+ OutputStream serverOutStream = serverSocket.getOutputStream();
+ serverOutStream.write(3);
+ InputStream clientInStream = clientSocket.getInputStream();
+ assertEquals(3, clientInStream.read());
+
+ // Test sending and receiving file descriptors
+ clientSocket.setFileDescriptorsForSend(new FileDescriptor[]{FileDescriptor.in});
+ clientOutStream.write(32);
+ assertEquals(32, serverInStream.read());
+
+ FileDescriptor[] out = serverSocket.getAncillaryFileDescriptors();
+ assertEquals(1, out.length);
+ FileDescriptor fd = clientSocket.getFileDescriptor();
+ assertTrue(fd.valid());
+
+ //shutdown input stream of client
+ clientSocket.shutdownInput();
+ assertEquals(-1, clientInStream.read());
+
+ //shutdown output stream of client
+ clientSocket.shutdownOutput();
+ try {
+ clientOutStream.write(10);
+ fail("testLocalSocket shouldn't come to here");
+ } catch (IOException e) {
+ // expected
+ }
+
+ //shutdown input stream of server
+ serverSocket.shutdownInput();
+ assertEquals(-1, serverInStream.read());
+
+ //shutdown output stream of server
+ serverSocket.shutdownOutput();
+ try {
+ serverOutStream.write(10);
+ fail("testLocalSocket shouldn't come to here");
+ } catch (IOException e) {
+ // expected
+ }
+
+ //close client socket
+ clientSocket.close();
+ try {
+ clientInStream.read();
+ fail("testLocalSocket shouldn't come to here");
+ } catch (IOException e) {
+ // expected
+ }
+
+ //close server socket
+ serverSocket.close();
+ try {
+ serverInStream.read();
+ fail("testLocalSocket shouldn't come to here");
+ } catch (IOException e) {
+ // expected
+ }
+ }
+
+ public void testAccessors() throws IOException {
+ String address = ADDRESS_PREFIX + "_testAccessors";
+ LocalSocket socket = new LocalSocket();
+ LocalSocketAddress addr = new LocalSocketAddress(address);
+
+ assertFalse(socket.isBound());
+ socket.bind(addr);
+ assertTrue(socket.isBound());
+ assertEquals(addr, socket.getLocalSocketAddress());
+
+ String str = socket.toString();
+ assertTrue(str.contains("impl:android.net.LocalSocketImpl"));
+
+ socket.setReceiveBufferSize(1999);
+ assertEquals(1999 << 1, socket.getReceiveBufferSize());
+
+ socket.setSendBufferSize(3998);
+ assertEquals(3998 << 1, socket.getSendBufferSize());
+
+ assertEquals(0, socket.getSoTimeout());
+ socket.setSoTimeout(1996);
+ assertTrue(socket.getSoTimeout() > 0);
+
+ try {
+ socket.getRemoteSocketAddress();
+ fail("testLocalSocketSecondary shouldn't come to here");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ try {
+ socket.isClosed();
+ fail("testLocalSocketSecondary shouldn't come to here");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ try {
+ socket.isInputShutdown();
+ fail("testLocalSocketSecondary shouldn't come to here");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ try {
+ socket.isOutputShutdown();
+ fail("testLocalSocketSecondary shouldn't come to here");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ try {
+ socket.connect(addr, 2005);
+ fail("testLocalSocketSecondary shouldn't come to here");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ socket.close();
+ }
+
+ // http://b/31205169
+ public void testSetSoTimeout_readTimeout() throws Exception {
+ String address = ADDRESS_PREFIX + "_testSetSoTimeout_readTimeout";
+
+ try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+ final LocalSocket clientSocket = socketPair.clientSocket;
+
+ // Set the timeout in millis.
+ int timeoutMillis = 1000;
+ clientSocket.setSoTimeout(timeoutMillis);
+
+ // Avoid blocking the test run if timeout doesn't happen by using a separate thread.
+ Callable<Result> reader = () -> {
+ try {
+ clientSocket.getInputStream().read();
+ return Result.noException("Did not block");
+ } catch (IOException e) {
+ return Result.exception(e);
+ }
+ };
+ // Allow the configured timeout, plus some slop.
+ int allowedTime = timeoutMillis + 2000;
+ Result result = runInSeparateThread(allowedTime, reader);
+
+ // Check the message was a timeout, it's all we have to go on.
+ String expectedMessage = Os.strerror(OsConstants.EAGAIN);
+ result.assertThrewIOException(expectedMessage);
+ }
+ }
+
+ // http://b/31205169
+ public void testSetSoTimeout_writeTimeout() throws Exception {
+ String address = ADDRESS_PREFIX + "_testSetSoTimeout_writeTimeout";
+
+ try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+ final LocalSocket clientSocket = socketPair.clientSocket;
+
+ // Set the timeout in millis.
+ int timeoutMillis = 1000;
+ clientSocket.setSoTimeout(timeoutMillis);
+
+ // Set a small buffer size so we know we can flood it.
+ clientSocket.setSendBufferSize(100);
+ final int bufferSize = clientSocket.getSendBufferSize();
+
+ // Avoid blocking the test run if timeout doesn't happen by using a separate thread.
+ Callable<Result> writer = () -> {
+ try {
+ byte[] toWrite = new byte[bufferSize * 2];
+ clientSocket.getOutputStream().write(toWrite);
+ return Result.noException("Did not block");
+ } catch (IOException e) {
+ return Result.exception(e);
+ }
+ };
+ // Allow the configured timeout, plus some slop.
+ int allowedTime = timeoutMillis + 2000;
+
+ Result result = runInSeparateThread(allowedTime, writer);
+
+ // Check the message was a timeout, it's all we have to go on.
+ String expectedMessage = Os.strerror(OsConstants.EAGAIN);
+ result.assertThrewIOException(expectedMessage);
+ }
+ }
+
+ public void testAvailable() throws Exception {
+ String address = ADDRESS_PREFIX + "_testAvailable";
+
+ try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+ LocalSocket clientSocket = socketPair.clientSocket;
+ LocalSocket serverSocket = socketPair.serverSocket.accept();
+
+ OutputStream clientOutputStream = clientSocket.getOutputStream();
+ InputStream serverInputStream = serverSocket.getInputStream();
+ assertEquals(0, serverInputStream.available());
+
+ byte[] buffer = new byte[50];
+ clientOutputStream.write(buffer);
+ assertEquals(50, serverInputStream.available());
+
+ InputStream clientInputStream = clientSocket.getInputStream();
+ OutputStream serverOutputStream = serverSocket.getOutputStream();
+ assertEquals(0, clientInputStream.available());
+ serverOutputStream.write(buffer);
+ assertEquals(50, serverInputStream.available());
+
+ serverSocket.close();
+ }
+ }
+
+ // http://b/34095140
+ public void testLocalSocketCreatedFromFileDescriptor() throws Exception {
+ String address = ADDRESS_PREFIX + "_testLocalSocketCreatedFromFileDescriptor";
+
+ // Establish connection between a local client and server to get a valid client socket file
+ // descriptor.
+ try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+ // Extract the client FileDescriptor we can use.
+ FileDescriptor fileDescriptor = socketPair.clientSocket.getFileDescriptor();
+ assertTrue(fileDescriptor.valid());
+
+ // Create the LocalSocket we want to test.
+ LocalSocket clientSocketCreatedFromFileDescriptor =
+ LocalSocket.createConnectedLocalSocket(fileDescriptor);
+ assertTrue(clientSocketCreatedFromFileDescriptor.isConnected());
+ assertTrue(clientSocketCreatedFromFileDescriptor.isBound());
+
+ // Test the LocalSocket can be used for communication.
+ LocalSocket serverSocket = socketPair.serverSocket.accept();
+ OutputStream clientOutputStream =
+ clientSocketCreatedFromFileDescriptor.getOutputStream();
+ InputStream serverInputStream = serverSocket.getInputStream();
+
+ clientOutputStream.write(12);
+ assertEquals(12, serverInputStream.read());
+
+ // Closing clientSocketCreatedFromFileDescriptor does not close the file descriptor.
+ clientSocketCreatedFromFileDescriptor.close();
+ assertTrue(fileDescriptor.valid());
+
+ // .. while closing the LocalSocket that owned the file descriptor does.
+ socketPair.clientSocket.close();
+ assertFalse(fileDescriptor.valid());
+ }
+ }
+
+ public void testFlush() throws Exception {
+ String address = ADDRESS_PREFIX + "_testFlush";
+
+ try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+ LocalSocket clientSocket = socketPair.clientSocket;
+ LocalSocket serverSocket = socketPair.serverSocket.accept();
+
+ OutputStream clientOutputStream = clientSocket.getOutputStream();
+ InputStream serverInputStream = serverSocket.getInputStream();
+ testFlushWorks(clientOutputStream, serverInputStream);
+
+ OutputStream serverOutputStream = serverSocket.getOutputStream();
+ InputStream clientInputStream = clientSocket.getInputStream();
+ testFlushWorks(serverOutputStream, clientInputStream);
+
+ serverSocket.close();
+ }
+ }
+
+ private void testFlushWorks(OutputStream outputStream, InputStream inputStream)
+ throws Exception {
+ final int bytesToTransfer = 50;
+ StreamReader inputStreamReader = new StreamReader(inputStream, bytesToTransfer);
+
+ byte[] buffer = new byte[bytesToTransfer];
+ outputStream.write(buffer);
+ assertEquals(bytesToTransfer, inputStream.available());
+
+ // Start consuming the data.
+ inputStreamReader.start();
+
+ // This doesn't actually flush any buffers, it just polls until the reader has read all the
+ // bytes.
+ outputStream.flush();
+
+ inputStreamReader.waitForCompletion(5000);
+ inputStreamReader.assertBytesRead(bytesToTransfer);
+ assertEquals(0, inputStream.available());
+ }
+
+ private static class StreamReader extends Thread {
+ private final InputStream is;
+ private final int expectedByteCount;
+ private final CountDownLatch completeLatch = new CountDownLatch(1);
+
+ private volatile Exception exception;
+ private int bytesRead;
+
+ private StreamReader(InputStream is, int expectedByteCount) {
+ this.is = is;
+ this.expectedByteCount = expectedByteCount;
+ }
+
+ @Override
+ public void run() {
+ try {
+ byte[] buffer = new byte[10];
+ int readCount;
+ while ((readCount = is.read(buffer)) >= 0) {
+ bytesRead += readCount;
+ if (bytesRead >= expectedByteCount) {
+ break;
+ }
+ }
+ } catch (IOException e) {
+ exception = e;
+ } finally {
+ completeLatch.countDown();
+ }
+ }
+
+ public void waitForCompletion(long waitMillis) throws Exception {
+ if (!completeLatch.await(waitMillis, TimeUnit.MILLISECONDS)) {
+ fail("Timeout waiting for completion");
+ }
+ if (exception != null) {
+ throw new Exception("Read failed", exception);
+ }
+ }
+
+ public void assertBytesRead(int expected) {
+ assertEquals(expected, bytesRead);
+ }
+ }
+
+ private static class Result {
+ private final String type;
+ private final Exception e;
+
+ private Result(String type, Exception e) {
+ this.type = type;
+ this.e = e;
+ }
+
+ static Result noException(String description) {
+ return new Result(description, null);
+ }
+
+ static Result exception(Exception e) {
+ return new Result(e.getClass().getName(), e);
+ }
+
+ void assertThrewIOException(String expectedMessage) {
+ assertEquals("Unexpected result type", IOException.class.getName(), type);
+ assertEquals("Unexpected exception message", expectedMessage, e.getMessage());
+ }
+ }
+
+ private static Result runInSeparateThread(int allowedTime, final Callable<Result> callable)
+ throws Exception {
+ ExecutorService service = Executors.newSingleThreadScheduledExecutor();
+ Future<Result> future = service.submit(callable);
+ Result result = future.get(allowedTime, TimeUnit.MILLISECONDS);
+ if (!future.isDone()) {
+ fail("Worker thread appears blocked");
+ }
+ return result;
+ }
+
+ private static class LocalSocketPair implements AutoCloseable {
+ static LocalSocketPair createConnectedSocketPair(String address) throws Exception {
+ LocalServerSocket localServerSocket = new LocalServerSocket(address);
+ final LocalSocket clientSocket = new LocalSocket();
+
+ // Establish connection between client and server
+ LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
+ clientSocket.connect(locSockAddr);
+ assertTrue(clientSocket.isConnected());
+ return new LocalSocketPair(localServerSocket, clientSocket);
+ }
+
+ final LocalServerSocket serverSocket;
+ final LocalSocket clientSocket;
+
+ LocalSocketPair(LocalServerSocket serverSocket, LocalSocket clientSocket) {
+ this.serverSocket = serverSocket;
+ this.clientSocket = clientSocket;
+ }
+
+ public void close() throws Exception {
+ serverSocket.close();
+ clientSocket.close();
+ }
+ }
+}
diff --git a/tests/providerui/AndroidManifest.xml b/tests/providerui/AndroidManifest.xml
index 2f1f791..a14df70 100644
--- a/tests/providerui/AndroidManifest.xml
+++ b/tests/providerui/AndroidManifest.xml
@@ -23,9 +23,7 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<!--
@@ -42,7 +40,7 @@
</intent>
</queries>
- <application android:requestLegacyExternalStorage = "true">
+ <application>
<uses-library android:name="android.test.runner"/>
<activity android:name="android.providerui.cts.GetResultActivity" />
diff --git a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
index 4342810..542b3aa 100644
--- a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
+++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
@@ -16,6 +16,8 @@
package android.providerui.cts;
+import static android.provider.cts.ProviderTestUtils.resolveVolumeName;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -26,12 +28,12 @@
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.ContentResolver;
+import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.UriPermission;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
@@ -39,7 +41,6 @@
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
-import android.os.UserManager;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.cts.ProviderTestUtils;
@@ -50,12 +51,12 @@
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiScrollable;
import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.Until;
import android.system.Os;
import android.text.format.DateUtils;
import android.util.Log;
+import android.util.Pair;
import androidx.test.InstrumentationRegistry;
@@ -68,6 +69,7 @@
import org.junit.runners.Parameterized.Parameters;
import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -122,6 +124,7 @@
mActivity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
mInstrumentation.waitForIdleSync();
mActivity.clearResult();
+ mDevice.wakeUp();
}
@After
@@ -147,6 +150,7 @@
if (!supportsHardware()) return;
prepareFile();
+ clearDocumentsUi();
final Uri treeUri = acquireAccess(mFile, Environment.DIRECTORY_DOCUMENTS);
assertNotNull(treeUri);
@@ -171,10 +175,11 @@
}
@Test
- public void testGetDocumentUri_ThrowsWithoutPermission() throws Exception {
+ public void testGetDocumentUri_throwsWithoutPermission() throws Exception {
if (!supportsHardware()) return;
prepareFile();
+ clearDocumentsUi();
try {
MediaStore.getDocumentUri(mActivity, mMediaStoreUri);
@@ -185,10 +190,11 @@
}
@Test
- public void testGetDocumentUri_Symmetry_ExternalStorageProvider() throws Exception {
+ public void testGetDocumentUri_symmetry_externalStorageProvider() throws Exception {
if (!supportsHardware()) return;
prepareFile();
+ clearDocumentsUi();
final Uri treeUri = acquireAccess(mFile, Environment.DIRECTORY_DOCUMENTS);
Log.v(TAG, "Tree " + treeUri);
@@ -207,10 +213,10 @@
}
@Test
- public void testGetMediaUriAccess_MediaDocumentsProvider() throws Exception {
+ public void testGetMediaUriAccess_mediaDocumentsProvider() throws Exception {
if (!supportsHardware()) return;
- prepareFile();
+ prepareFile("TEST");
clearDocumentsUi();
final Intent intent = new Intent();
intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
@@ -228,6 +234,104 @@
assertAccessToMediaUri(mediaUri, mFile);
}
+ @Test
+ public void testOpenFile_onMediaDocumentsProvider_success() throws Exception {
+ if (!supportsHardware()) return;
+
+ final String rawText = "TEST";
+ // Stage a text file which contains raw text "TEST"
+ prepareFile(rawText);
+ clearDocumentsUi();
+ final Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("*/*");
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ mDevice.waitForIdle();
+
+ findDocument(mFile.getName()).click();
+ final Result result = mActivity.getResult();
+ final Uri uri = result.data.getData();
+ assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, uri.getAuthority());
+
+ // Test reading
+ final byte[] expected = rawText.getBytes();
+ final byte[] actual = new byte[4];
+ try (ParcelFileDescriptor fd = mContext.getContentResolver()
+ .openFileDescriptor(uri, "r")) {
+ Os.read(fd.getFileDescriptor(), actual, 0, actual.length);
+ assertArrayEquals(expected, actual);
+ }
+
+ // Test write and read after it
+ final byte[] writtenText = "Hello World".getBytes();
+ final byte[] readText = new byte[11];
+ try (ParcelFileDescriptor fd = mContext.getContentResolver()
+ .openFileDescriptor(uri, "wt")) {
+ Os.write(fd.getFileDescriptor(), writtenText, 0, writtenText.length);
+ }
+ try (ParcelFileDescriptor fd = mContext.getContentResolver()
+ .openFileDescriptor(uri, "r")) {
+ Os.read(fd.getFileDescriptor(), readText, 0, readText.length);
+ assertArrayEquals(writtenText, readText);
+ }
+ }
+
+ @Test
+ public void testOpenFile_onMediaDocumentsProvider_failsWithoutAccess() throws Exception {
+ if (!supportsHardware()) return;
+
+ clearDocumentsUi();
+ final Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("*/*");
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ mDevice.waitForIdle();
+
+ String rawText = "TEST";
+ // Read and write grants will be provided to the file associated with this pair.
+ // Stages a text file which contains raw text "TEST"
+ Pair<Uri, File> uriFilePairWithGrants = prepareFileAndFetchDetails(rawText);
+ // Read and write grants will not be provided to the file associated with this pair
+ // Stages a text file which contains raw text "TEST"
+ Pair<Uri, File> uriFilePairWithoutGrants = prepareFileAndFetchDetails(rawText);
+ // Get access grants
+ findDocument(uriFilePairWithGrants.second.getName()).click();
+ final Result result = mActivity.getResult();
+ final Uri docUriOfFileWithAccess = result.data.getData();
+ // Creating doc URI for file by string replacement
+ Uri docUriOfFileWithoutAccess = Uri.parse(docUriOfFileWithAccess.toSafeString().replaceAll(
+ String.valueOf(ContentUris.parseId(uriFilePairWithGrants.first)),
+ String.valueOf(ContentUris.parseId(uriFilePairWithoutGrants.first))));
+
+ try {
+ assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, docUriOfFileWithAccess.getAuthority());
+ assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY,
+ docUriOfFileWithoutAccess.getAuthority());
+ // Test reading
+ try (ParcelFileDescriptor fd = mContext.getContentResolver().openFileDescriptor(
+ docUriOfFileWithoutAccess, "r")) {
+ fail("Expecting security exception as file does not have read grants which "
+ + "are provided through ACTION_OPEN_DOCUMENT intent.");
+ } catch (SecurityException expected) {
+ // Expected security exception as file does not have read grants
+ }
+ // Test writing
+ try (ParcelFileDescriptor fd = mContext.getContentResolver().openFileDescriptor(
+ docUriOfFileWithoutAccess, "wt")) {
+ fail("Expecting security exception as file does not have write grants which "
+ + "are provided through ACTION_OPEN_DOCUMENT intent.");
+ } catch (SecurityException expected) {
+ // Expected security exception as file does not have write grants
+ }
+ } finally {
+ // Deleting files
+ uriFilePairWithGrants.second.delete();
+ uriFilePairWithoutGrants.second.delete();
+ }
+ }
+
private void assertAccessToMediaUri(Uri mediaUri, File file) {
final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
try (Cursor c = mContext.getContentResolver().query(
@@ -310,7 +414,7 @@
}
private void prepareFile() throws Exception {
- final File dir = new File(getVolumePath(mVolumeName),
+ final File dir = new File(getVolumePath(resolveVolumeName(mVolumeName)),
Environment.DIRECTORY_DOCUMENTS);
final File file = new File(dir, "cts" + System.nanoTime() + ".txt");
@@ -320,6 +424,29 @@
Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri);
}
+ private void prepareFile(String rawText) throws Exception {
+ final File dir = new File(getVolumePath(resolveVolumeName(mVolumeName)),
+ Environment.DIRECTORY_DOCUMENTS);
+ final File file = new File(dir, "cts" + System.nanoTime() + ".txt");
+
+ mFile = stageFileWithRawText(rawText, file);
+ mMediaStoreUri = MediaStore.scanFile(mContext.getContentResolver(), mFile);
+
+ Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri);
+ }
+
+ private Pair<Uri, File> prepareFileAndFetchDetails(String rawText) throws Exception {
+ final File dir = new File(getVolumePath(resolveVolumeName(mVolumeName)),
+ Environment.DIRECTORY_DOCUMENTS);
+ final File file = new File(dir, "cts" + System.nanoTime() + ".txt");
+
+ File stagedFile = stageFileWithRawText(rawText, file);
+
+ Uri uri = MediaStore.scanFile(mContext.getContentResolver(), stagedFile);
+ Log.v(TAG, "Staged " + stagedFile + " as " + uri);
+ return Pair.create(uri, stagedFile);
+ }
+
private void assertToolbarTitleEquals(String targetPackageName, String label)
throws UiObjectNotFoundException {
final UiSelector toolbarUiSelector = new UiSelector().resourceId(
@@ -427,32 +554,28 @@
// The caller may be trying to stage into a location only available to
// the shell user, so we need to perform the entire copy as the shell
final Context context = InstrumentationRegistry.getTargetContext();
- UserManager userManager = context.getSystemService(UserManager.class);
- if (userManager.isSystemUser() &&
- FileUtils.contains(Environment.getStorageDirectory(), file)) {
- executeShellCommand("mkdir -p " + file.getParent());
+ final File dir = file.getParentFile();
+ dir.mkdirs();
+ if (!dir.exists()) {
+ throw new FileNotFoundException("Failed to create parent for " + file);
+ }
+ try (InputStream source = context.getResources().openRawResource(resId);
+ OutputStream target = new FileOutputStream(file)) {
+ FileUtils.copy(source, target);
+ }
+ return file;
+ }
- try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId)) {
- final File source = ParcelFileDescriptor.getFile(afd.getFileDescriptor());
- final long skip = afd.getStartOffset();
- final long count = afd.getLength();
-
- executeShellCommand(String.format("dd bs=1 if=%s skip=%d count=%d of=%s",
- source.getAbsolutePath(), skip, count, file.getAbsolutePath()));
-
- // Force sync to try updating other views
- executeShellCommand("sync");
- }
- } else {
- final File dir = file.getParentFile();
- dir.mkdirs();
- if (!dir.exists()) {
- throw new FileNotFoundException("Failed to create parent for " + file);
- }
- try (InputStream source = context.getResources().openRawResource(resId);
- OutputStream target = new FileOutputStream(file)) {
- FileUtils.copy(source, target);
- }
+ static File stageFileWithRawText(String rawText, File file) throws IOException {
+ final File dir = file.getParentFile();
+ dir.mkdirs();
+ if (!dir.exists()) {
+ throw new FileNotFoundException("Failed to create parent for " + file);
+ }
+ try (InputStream source = new ByteArrayInputStream(
+ rawText.getBytes(StandardCharsets.UTF_8));
+ OutputStream target = new FileOutputStream(file)) {
+ FileUtils.copy(source, target);
}
return file;
}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index a20a8f9..c56103c 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -259,6 +259,19 @@
@AppModeFull(reason = "No usage events access in instant apps")
@Test
+ public void testLastTimeVisible_launchActivityShouldBeDetected() throws Exception {
+ mUiDevice.wakeUp();
+ dismissKeyguard(); // also want to start out with the keyguard dismissed.
+
+ final long startTime = System.currentTimeMillis();
+ launchSubActivity(Activities.ActivityOne.class);
+ final long endTime = System.currentTimeMillis();
+
+ verifyLastTimeVisibleWithinRange(startTime, endTime, mTargetPackage);
+ }
+
+ @AppModeFull(reason = "No usage events access in instant apps")
+ @Test
public void testLastTimeAnyComponentUsed_launchActivityShouldBeDetected() throws Exception {
mUiDevice.wakeUp();
dismissKeyguard(); // also want to start out with the keyguard dismissed.
@@ -311,6 +324,17 @@
verifyLastTimeAnyComponentUsedWithinRange(startTime, endTime, TEST_APP_PKG);
}
+ private void verifyLastTimeVisibleWithinRange(
+ long startTime, long endTime, String targetPackage) {
+ final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(
+ startTime, endTime);
+ final UsageStats stats = map.get(targetPackage);
+ assertNotNull(stats);
+ final long lastTimeVisible = stats.getLastTimeVisible();
+ assertLessThanOrEqual(startTime, lastTimeVisible);
+ assertLessThanOrEqual(lastTimeVisible, endTime);
+ }
+
private void verifyLastTimeAnyComponentUsedWithinRange(
long startTime, long endTime, String targetPackage) {
final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(
diff --git a/tests/tests/appop/Android.bp b/tests/tests/appop/Android.bp
index 33e53a5..bd34b7d 100644
--- a/tests/tests/appop/Android.bp
+++ b/tests/tests/appop/Android.bp
@@ -82,7 +82,7 @@
"libbacktrace",
"libbase",
"libbinder",
- "libbpf",
+ "libbpf_bcc",
"libbpf_android",
"libc++",
"libcgrouprc",
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index c03dd80..e39bf36 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -39,7 +39,6 @@
<option name="push" value="CtsContentLongSharedUserIdTestApp.apk->/data/local/tmp/cts/content/CtsContentLongSharedUserIdTestApp.apk" />
<option name="push" value="CtsContentMaxPackageNameTestApp.apk->/data/local/tmp/cts/content/CtsContentMaxPackageNameTestApp.apk" />
<option name="push" value="CtsContentMaxSharedUserIdTestApp.apk->/data/local/tmp/cts/content/CtsContentMaxSharedUserIdTestApp.apk" />
- <option name="push" value="CtsContentLongLabelNameTestApp.apk->/data/local/tmp/cts/content/CtsContentLongLabelNameTestApp.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
diff --git a/tests/tests/content/BinderPermissionTestService/Android.bp b/tests/tests/content/BinderPermissionTestService/Android.bp
index 928e532..8ba2432 100644
--- a/tests/tests/content/BinderPermissionTestService/Android.bp
+++ b/tests/tests/content/BinderPermissionTestService/Android.bp
@@ -27,6 +27,7 @@
"aidl/**/I*.aidl",
],
test_suites: [
+ "mts",
"cts",
"general-tests",
],
diff --git a/tests/tests/content/DirectBootUnawareTestApp/Android.bp b/tests/tests/content/DirectBootUnawareTestApp/Android.bp
index 4a1a9bb..8c35eb4 100644
--- a/tests/tests/content/DirectBootUnawareTestApp/Android.bp
+++ b/tests/tests/content/DirectBootUnawareTestApp/Android.bp
@@ -22,6 +22,7 @@
sdk_version: "current",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
diff --git a/tests/tests/content/HelloWorldApp/Android.bp b/tests/tests/content/HelloWorldApp/Android.bp
index e3c2de5..cb45323 100644
--- a/tests/tests/content/HelloWorldApp/Android.bp
+++ b/tests/tests/content/HelloWorldApp/Android.bp
@@ -42,6 +42,7 @@
srcs: ["src5/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -56,6 +57,7 @@
manifest: "AndroidManifestProfileable.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"vts10",
"general-tests",
@@ -70,6 +72,7 @@
srcs: ["src7/**/*.java"],
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -105,6 +108,7 @@
],
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -120,6 +124,7 @@
manifest: "AndroidManifestShell.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"vts10",
"general-tests",
diff --git a/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp b/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
index c24d69a..c211c0c 100644
--- a/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
+++ b/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
@@ -22,6 +22,7 @@
sdk_version: "current",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
diff --git a/tests/tests/content/SyncAccountAccessStubs/Android.bp b/tests/tests/content/SyncAccountAccessStubs/Android.bp
index e8904d7..9f1e9ab 100644
--- a/tests/tests/content/SyncAccountAccessStubs/Android.bp
+++ b/tests/tests/content/SyncAccountAccessStubs/Android.bp
@@ -25,6 +25,7 @@
srcs: ["src/**/*.java"],
sdk_version: "current",
test_suites: [
+ "mts",
"cts",
"general-tests",
],
diff --git a/tests/tests/content/emptytestapp/Android.bp b/tests/tests/content/emptytestapp/Android.bp
index f66ccdf..70c87fe 100644
--- a/tests/tests/content/emptytestapp/Android.bp
+++ b/tests/tests/content/emptytestapp/Android.bp
@@ -22,6 +22,7 @@
sdk_version: "current",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -35,6 +36,7 @@
manifest: "AndroidManifestLongPackageName.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -48,6 +50,7 @@
manifest: "AndroidManifestLongSharedUserId.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -61,6 +64,7 @@
manifest: "AndroidManifestMaxPackageName.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
@@ -73,20 +77,8 @@
manifest: "AndroidManifestMaxSharedUserId.xml",
// tag this module as a cts test artifact
test_suites: [
+ "mts",
"cts",
"general-tests",
],
}
-
-android_test_helper_app {
- name: "CtsContentLongLabelNameTestApp",
- defaults: ["cts_defaults"],
- sdk_version: "current",
- manifest: "AndroidManifestLongLabelName.xml",
- // tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- ],
- min_sdk_version : "29"
-}
diff --git a/tests/tests/content/emptytestapp/AndroidManifestLongLabelName.xml b/tests/tests/content/emptytestapp/AndroidManifestLongLabelName.xml
deleted file mode 100644
index eaa69b1..0000000
--- a/tests/tests/content/emptytestapp/AndroidManifestLongLabelName.xml
+++ /dev/null
@@ -1,23 +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.
--->
-<!-- Test that the maximum length of the label returned from the load label api should be 1000 -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.content.cts.emptytestapp" >
- <application android:hasCode="false" android:label="@string/long_app_name">
- <activity android:name=".MockActivity" android:label="@string/long_app_name"
- android:exported="true" android:enabled="true" />
- </application>
-</manifest>
diff --git a/tests/tests/content/emptytestapp/res/values/strings.xml b/tests/tests/content/emptytestapp/res/values/strings.xml
deleted file mode 100644
index 8c06d18..0000000
--- a/tests/tests/content/emptytestapp/res/values/strings.xml
+++ /dev/null
@@ -1,31 +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.
--->
-
-<resources>
- <string name="long_app_name">
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
- </string>
-</resources>
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index df40ea9..3d967d7 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -28,25 +28,12 @@
import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.pm.ApkChecksum;
-import android.content.pm.ApplicationInfo;
import android.content.pm.DataLoaderParams;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
-import android.os.Bundle;
-import android.os.ConditionVariable;
-import android.os.IBinder;
import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
import android.platform.test.annotations.AppModeFull;
import androidx.test.InstrumentationRegistry;
@@ -70,10 +57,6 @@
import java.util.Arrays;
import java.util.Optional;
import java.util.Random;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@RunWith(Parameterized.class)
@@ -171,15 +154,6 @@
}
}
- private static void writeFileToSession(PackageInstaller.Session session, String name,
- String apk) throws IOException {
- File file = new File(createApkPath(apk));
- try (OutputStream os = session.openWrite(name, 0, file.length());
- InputStream is = new FileInputStream(file)) {
- writeFullStream(is, os, file.length());
- }
- }
-
@Before
public void onBefore() throws Exception {
// Check if Incremental is allowed and revert to non-dataloader installation.
@@ -508,78 +482,6 @@
}
@Test
- public void testDontKillWithSplit() throws Exception {
- installPackage(TEST_HW5);
-
- getUiAutomation().adoptShellPermissionIdentity();
- try {
- final PackageInstaller installer = getPackageInstaller();
- final SessionParams params = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
- params.setAppPackageName(TEST_APP_PACKAGE);
- params.setDontKillApp(true);
-
- final int sessionId = installer.createSession(params);
- PackageInstaller.Session session = installer.openSession(sessionId);
- assertTrue((session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0);
-
- writeFileToSession(session, "hw5_split0", TEST_HW5_SPLIT0);
-
- final CompletableFuture<Boolean> result = new CompletableFuture<>();
- session.commit(new IntentSender((IIntentSender) new IIntentSender.Stub() {
- @Override
- public void send(int code, Intent intent, String resolvedType,
- IBinder whitelistToken, IIntentReceiver finishedReceiver,
- String requiredPermission, Bundle options) throws RemoteException {
- boolean dontKillApp =
- (session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0;
- result.complete(dontKillApp);
- }
- }));
-
- // We are adding split. OK to have the flag.
- assertTrue(result.get());
- } finally {
- getUiAutomation().dropShellPermissionIdentity();
- }
- }
-
- @Test
- public void testDontKillRemovedWithBaseApk() throws Exception {
- installPackage(TEST_HW5);
-
- getUiAutomation().adoptShellPermissionIdentity();
- try {
- final PackageInstaller installer = getPackageInstaller();
- final SessionParams params = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
- params.setAppPackageName(TEST_APP_PACKAGE);
- params.setDontKillApp(true);
-
- final int sessionId = installer.createSession(params);
- PackageInstaller.Session session = installer.openSession(sessionId);
- assertTrue((session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0);
-
- writeFileToSession(session, "hw7", TEST_HW7);
-
- final CompletableFuture<Boolean> result = new CompletableFuture<>();
- session.commit(new IntentSender((IIntentSender) new IIntentSender.Stub() {
- @Override
- public void send(int code, Intent intent, String resolvedType,
- IBinder whitelistToken, IIntentReceiver finishedReceiver,
- String requiredPermission, Bundle options) throws RemoteException {
- boolean dontKillApp =
- (session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0;
- result.complete(dontKillApp);
- }
- }));
-
- // We are updating base.apk. Flag to be removed.
- assertFalse(result.get());
- } finally {
- getUiAutomation().dropShellPermissionIdentity();
- }
- }
-
- @Test
public void testDataLoaderParamsApiV1() throws Exception {
if (!mStreaming) {
return;
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index 262c687..71648b3 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -154,8 +154,6 @@
+ "CtsContentMaxPackageNameTestApp.apk";
private static final String MAX_SHARED_USER_ID_APK = SAMPLE_APK_BASE
+ "CtsContentMaxSharedUserIdTestApp.apk";
- private static final String LONG_LABEL_NAME_APK = SAMPLE_APK_BASE
- + "CtsContentLongLabelNameTestApp.apk";
private static final String EMPTY_APP_PACKAGE_NAME = "android.content.cts.emptytestapp";
private static final String EMPTY_APP_MAX_PACKAGE_NAME = "android.content.cts.emptytestapp27j"
+ "EBRNRG3ozwBsGr1sVIM9U0bVTI2TdyIyeRkZgW4JrJefwNIBAmCg4AzqXiCvG6JjqA0uTCWSFu2YqAVxVd"
@@ -165,8 +163,6 @@
private static final String HELLO_WORLD_PACKAGE_NAME = "com.example.helloworld";
private static final String HELLO_WORLD_APK = SAMPLE_APK_BASE + "HelloWorld5.apk";
- private static final int MAX_SAFE_LABEL_LENGTH = 1000;
-
@Before
public void setup() throws Exception {
mContext = InstrumentationRegistry.getContext();
@@ -1485,33 +1481,4 @@
private void uninstallPackage(String packageName) {
SystemUtil.runShellCommand("pm uninstall " + packageName);
}
-
- @Test
- public void loadApplicationLabel_withLongLabelName_truncated() throws Exception {
- assertThat(installPackage(LONG_LABEL_NAME_APK)).isTrue();
- final ApplicationInfo info = mPackageManager.getApplicationInfo(
- EMPTY_APP_PACKAGE_NAME, 0 /* flags */);
- final CharSequence resLabel = mPackageManager.getText(
- EMPTY_APP_PACKAGE_NAME, info.labelRes, info);
-
- assertThat(resLabel.length()).isGreaterThan(MAX_SAFE_LABEL_LENGTH);
- assertThat(info.loadLabel(mPackageManager).length()).isEqualTo(MAX_SAFE_LABEL_LENGTH);
- }
-
- @Test
- public void loadComponentLabel_withLongLabelName_truncated() throws Exception {
- assertThat(installPackage(LONG_LABEL_NAME_APK)).isTrue();
- final ComponentName componentName = ComponentName.createRelative(
- EMPTY_APP_PACKAGE_NAME, ".MockActivity");
- final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
- EMPTY_APP_PACKAGE_NAME, 0 /* flags */);
- final ActivityInfo activityInfo = mPackageManager.getActivityInfo(
- componentName, 0 /* flags */);
- final CharSequence resLabel = mPackageManager.getText(
- EMPTY_APP_PACKAGE_NAME, activityInfo.labelRes, appInfo);
-
- assertThat(resLabel.length()).isGreaterThan(MAX_SAFE_LABEL_LENGTH);
- assertThat(activityInfo.loadLabel(mPackageManager).length())
- .isEqualTo(MAX_SAFE_LABEL_LENGTH);
- }
}
diff --git a/tests/tests/icu/Android.bp b/tests/tests/icu/Android.bp
index c5a5141..26c195c 100644
--- a/tests/tests/icu/Android.bp
+++ b/tests/tests/icu/Android.bp
@@ -30,7 +30,6 @@
test_suites: [
"cts",
"general-tests",
- "mts",
],
platform_apis: true,
}
diff --git a/tests/tests/libcoreapievolution/Android.bp b/tests/tests/libcoreapievolution/Android.bp
index 891ebc3..eed4fc3 100644
--- a/tests/tests/libcoreapievolution/Android.bp
+++ b/tests/tests/libcoreapievolution/Android.bp
@@ -30,6 +30,6 @@
test_suites: [
"cts",
"general-tests",
- "mts",
+ "mts-art",
],
}
diff --git a/tests/tests/libcoreapievolution/AndroidTest.xml b/tests/tests/libcoreapievolution/AndroidTest.xml
index 08f47fd..79aa4b2 100644
--- a/tests/tests/libcoreapievolution/AndroidTest.xml
+++ b/tests/tests/libcoreapievolution/AndroidTest.xml
@@ -42,4 +42,7 @@
<!-- ART Mainline Module (external (AOSP) version). -->
<option name="mainline-module-package-name" value="com.android.art" />
</object>
+
+ <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
</configuration>
diff --git a/tests/tests/libcorefileio/Android.bp b/tests/tests/libcorefileio/Android.bp
index 58c388c..3febb32 100644
--- a/tests/tests/libcorefileio/Android.bp
+++ b/tests/tests/libcorefileio/Android.bp
@@ -30,6 +30,6 @@
test_suites: [
"cts",
"general-tests",
- "mts",
+ "mts-art",
],
}
diff --git a/tests/tests/libcorefileio/AndroidTest.xml b/tests/tests/libcorefileio/AndroidTest.xml
index c90b702..2220973 100644
--- a/tests/tests/libcorefileio/AndroidTest.xml
+++ b/tests/tests/libcorefileio/AndroidTest.xml
@@ -40,4 +40,7 @@
<!-- ART Mainline Module (external (AOSP) version). -->
<option name="mainline-module-package-name" value="com.android.art" />
</object>
+
+ <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
</configuration>
diff --git a/tests/tests/libcorelegacy22/Android.bp b/tests/tests/libcorelegacy22/Android.bp
index 44a997a..98684b2 100644
--- a/tests/tests/libcorelegacy22/Android.bp
+++ b/tests/tests/libcorelegacy22/Android.bp
@@ -26,6 +26,6 @@
test_suites: [
"cts",
"general-tests",
- "mts",
+ "mts-art",
],
}
diff --git a/tests/tests/libcorelegacy22/AndroidTest.xml b/tests/tests/libcorelegacy22/AndroidTest.xml
index 94c1134..670a3af 100644
--- a/tests/tests/libcorelegacy22/AndroidTest.xml
+++ b/tests/tests/libcorelegacy22/AndroidTest.xml
@@ -42,4 +42,7 @@
<!-- ART Mainline Module (external (AOSP) version). -->
<option name="mainline-module-package-name" value="com.android.art" />
</object>
+
+ <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
</configuration>
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 20d0f50..7106a9a 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -387,13 +387,6 @@
}
public void testID3v240ExtHeader() {
- if(!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
- // The fix for b/154357105 was released in mainline release 30.09.007.01
- // See https://android-build.googleplex.com/builds/treetop/googleplex-android-review/11174063
- if (TestUtils.skipTestIfMainlineLessThan("com.google.android.media", 300900701)) {
- return;
- }
- }
setDataSourceFd("sinesweepid3v24ext.mp3");
assertEquals("Mime type was other than expected",
"audio/mpeg",
diff --git a/tests/tests/mediaparser/Android.bp b/tests/tests/mediaparser/Android.bp
index 138cb5a..d916d61 100644
--- a/tests/tests/mediaparser/Android.bp
+++ b/tests/tests/mediaparser/Android.bp
@@ -18,7 +18,10 @@
android_test {
name: "CtsMediaParserTestCases",
- defaults: ["CtsMediaParserTestCasesDefaults", "cts_defaults"],
+ defaults: [
+ "CtsMediaParserTestCasesDefaults",
+ "cts_defaults",
+ ],
min_sdk_version: "29",
test_suites: [
"cts",
@@ -39,8 +42,8 @@
static_libs: [
"ctstestrunner-axt",
"androidx.test.ext.junit",
- "exoplayer2-extractor-test-utils",
- "exoplayer2-extractor-tests-assets",
+ "exoplayer-cts_media-test_assets",
+ "exoplayer-cts_media-test_utils",
],
libs: [
"android.test.base",
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
index d5d01c8..0df3b1b 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
@@ -162,6 +162,8 @@
}
}
+ Preconditions.assertTestFileExists(mediaName);
+
File playbackOutput = new File(WorkDir.getTopDir(), "PlaybackTestResult.txt");
Writer output = new BufferedWriter(new FileWriter(playbackOutput, true));
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java
new file mode 100644
index 0000000..6fc2b8a
--- /dev/null
+++ b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediastress.cts;
+
+import java.io.File;
+
+import junit.framework.Assert;
+
+/**
+ * Static methods used to validate preconditions in the media CTS suite
+ * to simplify failure diagnosis.
+ */
+
+public final class Preconditions {
+ private static final String TAG = "Preconditions";
+
+ public static void assertTestFileExists(String pathName) {
+ File testFile = new File(pathName);
+ Assert.assertTrue("Test Setup Error, missing file: " + pathName, testFile.exists());
+ }
+
+ private Preconditions() {}
+}
diff --git a/tests/tests/mediatranscoding/Android.bp b/tests/tests/mediatranscoding/Android.bp
index 0b889ae..6a220a3 100644
--- a/tests/tests/mediatranscoding/Android.bp
+++ b/tests/tests/mediatranscoding/Android.bp
@@ -19,10 +19,12 @@
android_test {
name: "CtsMediaTranscodingTestCases",
defaults: ["CtsMediaTranscodingTestCasesDefaults", "cts_defaults"],
- min_sdk_version: "31",
+ // part of MTS, so we need compatibility back to Q/29
+ min_sdk_version: "29",
test_suites: [
"cts",
"general-tests",
+ "mts-media",
"mts",
],
static_libs: [
@@ -43,5 +45,4 @@
"android.test.base",
"android.test.runner",
],
- sdk_version: "test_current",
}
diff --git a/tests/tests/mediatranscoding/AndroidManifest.xml b/tests/tests/mediatranscoding/AndroidManifest.xml
index 1618b00..c69754a 100644
--- a/tests/tests/mediatranscoding/AndroidManifest.xml
+++ b/tests/tests/mediatranscoding/AndroidManifest.xml
@@ -25,6 +25,12 @@
<uses-library android:name="android.test.runner" />
</application>
+ <!-- included in mainline testing, so must run back on 29.
+ Transcoding was introduced in 31 -->
+ <uses-sdk android:minSdkVersion="29"
+ android:targetSdkVersion="31"/>
+
+
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.media.mediatranscoding.cts"
android:label="Tests for MediaTranscoding.">
diff --git a/tests/tests/mediatranscoding/OWNERS b/tests/tests/mediatranscoding/OWNERS
index e653979..a4393a7 100644
--- a/tests/tests/mediatranscoding/OWNERS
+++ b/tests/tests/mediatranscoding/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 761430
-hkuang@google.com
-chz@google.com
-lnilsson@google.com
+
+# go/android-fwk-media-solutions for info on areas of ownership.
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4
new file mode 100644
index 0000000..41f6c22
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4
new file mode 100644
index 0000000..7211fec1
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
index ba40ca5..6e63624 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
@@ -27,6 +27,8 @@
import android.test.AndroidTestCase;
import android.util.Xml;
+import androidx.test.filters.SdkSuppress;
+
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
@@ -36,6 +38,7 @@
@Presubmit
@AppModeFull(reason = "Instant apps cannot access the SD card")
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
public class ApplicationMediaCapabilitiesTest extends AndroidTestCase {
private static final String TAG = "ApplicationMediaCapabilitiesTest";
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
index c9b2b16..c265aa9 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
@@ -33,6 +33,7 @@
import android.media.MediaTranscodingManager.TranscodingSession;
import android.media.MediaTranscodingManager.VideoTranscodingRequest;
import android.net.Uri;
+// import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
@@ -44,6 +45,7 @@
import android.test.AndroidTestCase;
import android.util.Log;
+import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.MediaUtils;
@@ -70,6 +72,7 @@
@Presubmit
@RequiresDevice
@AppModeFull(reason = "Instant apps cannot access the SD card")
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
public class MediaTranscodingManagerTest extends AndroidTestCase {
private static final String TAG = "MediaTranscodingManagerTest";
/** The time to wait for the transcode operation to complete before failing the test. */
@@ -88,9 +91,11 @@
// Default setting for transcoding to H.264.
private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
- private static final int BIT_RATE = 20000000; // 20Mbps
- private static final int WIDTH = 1920;
- private static final int HEIGHT = 1080;
+ private static final int BIT_RATE = 4000000; // 4Mbps
+ private static final int WIDTH = 720;
+ private static final int HEIGHT = 480;
+ private static final int FRAME_RATE = 30;
+ private static final int INT_NOT_SET = Integer.MIN_VALUE;
// Threshold for the psnr to make sure the transcoded video is valid.
private static final int PSNR_THRESHOLD = 20;
@@ -128,9 +133,32 @@
/**
* Creates a MediaFormat with the default settings.
*/
- private static MediaFormat createMediaFormat() {
- MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
- format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+ private static MediaFormat createDefaultMediaFormat() {
+ return createMediaFormat(MIME_TYPE, WIDTH, HEIGHT, INT_NOT_SET /* frameRate */,
+ BIT_RATE /* bitrate */);
+ }
+
+ /**
+ * Creates a MediaFormat with custom settings.
+ */
+ private static MediaFormat createMediaFormat(String mime, int width, int height, int frameRate,
+ int bitrate) {
+ MediaFormat format = new MediaFormat();
+ if (mime != null) {
+ format.setString(MediaFormat.KEY_MIME, mime);
+ }
+ if (width != INT_NOT_SET) {
+ format.setInteger(MediaFormat.KEY_WIDTH, width);
+ }
+ if (height != INT_NOT_SET) {
+ format.setInteger(MediaFormat.KEY_HEIGHT, height);
+ }
+ if (frameRate != INT_NOT_SET) {
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
+ }
+ if (bitrate != INT_NOT_SET) {
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ }
return format;
}
@@ -148,9 +176,9 @@
androidx.test.InstrumentationRegistry.registerInstance(
InstrumentationRegistry.getInstrumentation(), new Bundle());
- // Setup source HEVC file uri.
- mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.Video_HEVC_30Frames,
- "Video_HEVC_30Frames.mp4");
+ // Setup default source HEVC 480p file uri.
+ mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.Video_HEVC_480p_30Frames,
+ "Video_HEVC_480p_30Frames.mp4");
// Setup source AVC file uri.
mSourceAVCVideoUri = resourceToUri(mContext, R.raw.Video_AVC_30Frames,
@@ -169,6 +197,7 @@
// Skip the test for TV, Car and Watch devices.
private boolean shouldSkip() {
+
PackageManager pm =
InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
return pm.hasSystemFeature(pm.FEATURE_LEANBACK) || pm.hasSystemFeature(pm.FEATURE_WATCH)
@@ -185,7 +214,7 @@
assertThrows(IllegalArgumentException.class, () -> {
VideoTranscodingRequest request =
new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, null,
- createMediaFormat())
+ createDefaultMediaFormat())
.build();
});
}
@@ -200,7 +229,7 @@
assertThrows(IllegalArgumentException.class, () -> {
VideoTranscodingRequest request =
new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, mDestinationUri,
- createMediaFormat())
+ createDefaultMediaFormat())
.setClientPid(-1)
.build();
});
@@ -216,7 +245,7 @@
assertThrows(IllegalArgumentException.class, () -> {
VideoTranscodingRequest request =
new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, mDestinationUri,
- createMediaFormat())
+ createDefaultMediaFormat())
.setClientUid(-1)
.build();
});
@@ -231,7 +260,8 @@
}
assertThrows(IllegalArgumentException.class, () -> {
VideoTranscodingRequest request =
- new VideoTranscodingRequest.Builder(null, mDestinationUri, createMediaFormat())
+ new VideoTranscodingRequest.Builder(null, mDestinationUri,
+ createDefaultMediaFormat())
.build();
});
}
@@ -245,7 +275,8 @@
}
assertThrows(IllegalArgumentException.class, () -> {
VideoTranscodingRequest request =
- new VideoTranscodingRequest.Builder(null, mDestinationUri, createMediaFormat())
+ new VideoTranscodingRequest.Builder(null, mDestinationUri,
+ createDefaultMediaFormat())
.build();
});
}
@@ -260,7 +291,7 @@
assertThrows(IllegalArgumentException.class, () -> {
VideoTranscodingRequest request =
new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, null,
- createMediaFormat())
+ createDefaultMediaFormat())
.build();
});
}
@@ -288,7 +319,7 @@
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
VideoTranscodingRequest request =
- new VideoTranscodingRequest.Builder(srcUri, dstUri, createMediaFormat())
+ new VideoTranscodingRequest.Builder(srcUri, dstUri, createDefaultMediaFormat())
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
@@ -340,7 +371,7 @@
// Tests transcoding to a uri in res folder and expects failure as test could not write to res
// folder.
public void testTranscodingToResFolder() throws Exception {
- if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+ if (shouldSkip()) {
return;
}
// Create a file Uri: android.resource://android.media.cts/temp.mp4
@@ -354,7 +385,7 @@
// Tests transcoding to a uri in internal cache folder and expects success.
public void testTranscodingToCacheDir() throws Exception {
- if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+ if (shouldSkip()) {
return;
}
// Create a file Uri: file:///data/user/0/android.media.cts/cache/temp.mp4
@@ -368,7 +399,7 @@
// Tests transcoding to a uri in internal files directory and expects success.
public void testTranscodingToInternalFilesDir() throws Exception {
- if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+ if (shouldSkip()) {
return;
}
// Create a file Uri: file:///data/user/0/android.media.cts/files/temp.mp4
@@ -379,6 +410,14 @@
TranscodingSession.RESULT_SUCCESS);
}
+ public void testHevcTranscoding720PVideo30FramesWithoutAudio() throws Exception {
+ if (shouldSkip()) {
+ return;
+ }
+ transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_720p_30Frames,
+ "Video_HEVC_720p_30Frames.mp4"), false /* testFileDescriptor */);
+ }
+
public void testAvcTranscoding1080PVideo30FramesWithoutAudio() throws Exception {
if (shouldSkip()) {
return;
@@ -550,6 +589,135 @@
stats.mAveragePSNR >= PSNR_THRESHOLD);
}
+ private void testVideoFormatResolverShouldTranscode(String mime, int width, int height,
+ int frameRate) {
+ ApplicationMediaCapabilities clientCaps =
+ new ApplicationMediaCapabilities.Builder().build();
+
+ MediaFormat mediaFormat = createMediaFormat(mime, width, height, frameRate, BIT_RATE);
+
+ TranscodingRequest.VideoFormatResolver
+ resolver = new TranscodingRequest.VideoFormatResolver(clientCaps,
+ mediaFormat);
+ assertTrue(resolver.shouldTranscode());
+ MediaFormat videoTrackFormat = resolver.resolveVideoFormat();
+ assertNotNull(videoTrackFormat);
+ }
+
+ public void testVideoFormatResolverValidArgs() {
+ if (shouldSkip()) {
+ return;
+ }
+ testVideoFormatResolverShouldTranscode(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT,
+ FRAME_RATE);
+ }
+
+ public void testVideoFormatResolverAv1Mime() {
+ if (shouldSkip()) {
+ return;
+ }
+ ApplicationMediaCapabilities clientCaps =
+ new ApplicationMediaCapabilities.Builder().build();
+
+ MediaFormat mediaFormat = createMediaFormat(MediaFormat.MIMETYPE_VIDEO_AV1, WIDTH, HEIGHT,
+ FRAME_RATE, BIT_RATE);
+
+ TranscodingRequest.VideoFormatResolver
+ resolver = new TranscodingRequest.VideoFormatResolver(clientCaps,
+ mediaFormat);
+ assertFalse(resolver.shouldTranscode());
+ MediaFormat videoTrackFormat = resolver.resolveVideoFormat();
+ assertNull(videoTrackFormat);
+ }
+
+ private void testVideoFormatResolverInvalidArgs(String mime, int width, int height,
+ int frameRate) {
+ ApplicationMediaCapabilities clientCaps =
+ new ApplicationMediaCapabilities.Builder().build();
+
+ MediaFormat mediaFormat = createMediaFormat(mime, width, height, frameRate, BIT_RATE);
+
+ TranscodingRequest.VideoFormatResolver
+ resolver = new TranscodingRequest.VideoFormatResolver(clientCaps,
+ mediaFormat);
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ MediaFormat videoTrackFormat = resolver.resolveVideoFormat();
+ });
+ }
+
+ public void testVideoFormatResolverZeroWidth() {
+ if (shouldSkip()) {
+ return;
+ }
+ testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, 0 /* width */,
+ HEIGHT, FRAME_RATE);
+ }
+
+ public void testVideoFormatResolverZeroHeight() {
+ if (shouldSkip()) {
+ return;
+ }
+ testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH,
+ 0 /* height */, FRAME_RATE);
+ }
+
+ public void testVideoFormatResolverZeroFrameRate() {
+ if (shouldSkip()) {
+ return;
+ }
+ testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH,
+ HEIGHT, 0 /* frameRate */);
+ }
+
+ public void testVideoFormatResolverNegativeWidth() {
+ if (shouldSkip()) {
+ return;
+ }
+ testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, -WIDTH,
+ HEIGHT, FRAME_RATE);
+ }
+
+ public void testVideoFormatResolverNegativeHeight() {
+ if (shouldSkip()) {
+ return;
+ }
+ testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH,
+ -HEIGHT, FRAME_RATE);
+ }
+
+ public void testVideoFormatResolverNegativeFrameRate() {
+ if (shouldSkip()) {
+ return;
+ }
+ testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH,
+ HEIGHT, -FRAME_RATE);
+ }
+
+ public void testVideoFormatResolverMissingWidth() {
+ if (shouldSkip()) {
+ return;
+ }
+ testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, INT_NOT_SET /* width*/,
+ HEIGHT /* height */, FRAME_RATE);
+ }
+
+ public void testVideoFormatResolverMissingHeight() {
+ if (shouldSkip()) {
+ return;
+ }
+ testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH,
+ INT_NOT_SET /* height */, FRAME_RATE);
+ }
+
+ public void testVideoFormatResolverMissingFrameRate() {
+ if (shouldSkip()) {
+ return;
+ }
+ testVideoFormatResolverShouldTranscode(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT,
+ INT_NOT_SET /* frameRate */);
+ }
+
private boolean compareFormat(MediaFormat fmt1, MediaFormat fmt2) {
if (fmt1 == fmt2) return true;
if (fmt1 == null || fmt2 == null) return false;
@@ -562,7 +730,7 @@
}
public void testCancelTranscoding() throws Exception {
- if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+ if (shouldSkip()) {
return;
}
Log.d(TAG, "Starting: testCancelTranscoding");
@@ -574,7 +742,7 @@
VideoTranscodingRequest request =
new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, destinationUri,
- createMediaFormat())
+ createDefaultMediaFormat())
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
@@ -617,43 +785,8 @@
assertTrue("Fails to cancel transcoding", finishedOnTime);
}
- // Transcoding video on behalf of init dameon and expect UnsupportedOperationException due to
- // CTS test is not a privilege caller.
- // Disable this test as Android S will only allow MediaProvider to access the API.
- /*public void testPidAndUidForwarding() throws Exception {
- if (shouldSkip()) {
- return;
- }
- assertThrows(UnsupportedOperationException.class, () -> {
- Semaphore transcodeCompleteSemaphore = new Semaphore(0);
-
- // Use init dameon's pid and uid.
- int pid = 1;
- int uid = 0;
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(mSourceHEVCVideoUri)
- .setDestinationUri(mDestinationUri)
- .setType(MediaTranscodingManager.TRANSCODING_TYPE_VIDEO)
- .setClientPid(pid)
- .setClientUid(uid)
- .setPriority(MediaTranscodingManager.PRIORITY_REALTIME)
- .setVideoTrackFormat(createMediaFormat())
- .build();
- Executor listenerExecutor = Executors.newSingleThreadExecutor();
-
- TranscodingSession session =
- mMediaTranscodingManager.enqueueRequest(
- request,
- listenerExecutor,
- transcodingSession -> {
- transcodeCompleteSemaphore.release();
- });
- });
- }*/
-
public void testTranscodingProgressUpdate() throws Exception {
- if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+ if (shouldSkip()) {
return;
}
Log.d(TAG, "Starting: testTranscodingProgressUpdate");
@@ -666,7 +799,7 @@
VideoTranscodingRequest request =
new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, destinationUri,
- createMediaFormat())
+ createDefaultMediaFormat())
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
@@ -705,7 +838,7 @@
}
public void testAddingClientUids() throws Exception {
- if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+ if (shouldSkip()) {
return;
}
Log.d(TAG, "Starting: testTranscodingProgressUpdate");
@@ -718,7 +851,7 @@
VideoTranscodingRequest request =
new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, destinationUri,
- createMediaFormat())
+ createDefaultMediaFormat())
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
@@ -782,27 +915,6 @@
return videoFormat;
}
- private boolean isVideoTranscodingSupported(Uri fileUri) throws IOException {
- MediaFormat sourceFormat = getVideoTrackFormat(fileUri);
- if (sourceFormat != null) {
- // Since destination format is not available, we assume width, height and
- // frame rate same as source format, and mime as AVC for destination format.
- MediaFormat destinationFormat = new MediaFormat();
- destinationFormat.setString(MediaFormat.KEY_MIME, MIME_TYPE);
- destinationFormat.setInteger(MediaFormat.KEY_WIDTH,
- sourceFormat.getInteger(MediaFormat.KEY_WIDTH));
- destinationFormat.setInteger(MediaFormat.KEY_HEIGHT,
- sourceFormat.getInteger(MediaFormat.KEY_HEIGHT));
- if (sourceFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {
- destinationFormat.setInteger(MediaFormat.KEY_FRAME_RATE,
- sourceFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
- }
- return isFormatSupported(sourceFormat, false)
- && isFormatSupported(destinationFormat, true);
- }
- return false;
- }
-
private boolean isFormatSupported(MediaFormat format, boolean isEncoder) {
String mime = format.getString(MediaFormat.KEY_MIME);
MediaCodec codec = null;
diff --git a/tests/tests/netpermission/OWNERS b/tests/tests/netpermission/OWNERS
deleted file mode 100644
index 370c20c..0000000
--- a/tests/tests/netpermission/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 31808
-include ../net/OWNERS
diff --git a/tests/tests/netpermission/internetpermission/AndroidManifest.xml b/tests/tests/netpermission/internetpermission/AndroidManifest.xml
deleted file mode 100644
index 45ef5bd..0000000
--- a/tests/tests/netpermission/internetpermission/AndroidManifest.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.networkpermission.internetpermission.cts">
-
- <application>
- <uses-library android:name="android.test.runner"/>
- <activity android:name="android.networkpermission.internetpermission.cts.InternetPermissionTest"
- android:label="InternetPermissionTest"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
- </intent-filter>
- </activity>
- </application>
-
- <!--
- The CTS stubs package cannot be used as the target application here,
- since that requires many permissions to be set. Instead, specify this
- package itself as the target and include any stub activities needed.
-
- This test package uses the default InstrumentationTestRunner, because
- the InstrumentationCtsTestRunner is only available in the stubs
- package. That runner cannot be added to this package either, since it
- relies on hidden APIs.
- -->
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.networkpermission.internetpermission.cts"
- android:label="CTS tests for INTERNET permissions">
- <meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener"/>
- </instrumentation>
-
-</manifest>
diff --git a/tests/tests/netpermission/internetpermission/AndroidTest.xml b/tests/tests/netpermission/internetpermission/AndroidTest.xml
deleted file mode 100644
index 3b23e72..0000000
--- a/tests/tests/netpermission/internetpermission/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<configuration description="Config for CTS internet permission test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="networking" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <option name="not-shardable" value="true" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsNetTestCasesInternetPermission.apk" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.networkpermission.internetpermission.cts" />
- <option name="runtime-hint" value="10s" />
- </test>
-</configuration>
diff --git a/tests/tests/netpermission/internetpermission/TEST_MAPPING b/tests/tests/netpermission/internetpermission/TEST_MAPPING
deleted file mode 100644
index 60877f4..0000000
--- a/tests/tests/netpermission/internetpermission/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsNetTestCasesInternetPermission"
- }
- ]
-}
diff --git a/tests/tests/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java b/tests/tests/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java
deleted file mode 100644
index 2b7c8b5..0000000
--- a/tests/tests/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java
+++ /dev/null
@@ -1,50 +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.net.cts.networkpermission.internetpermission;
-
-import static org.junit.Assert.fail;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.Socket;
-/**
-* Test that protected android.net.ConnectivityManager methods cannot be called without
-* permissions
-*/
-@RunWith(AndroidJUnit4.class)
-public class InternetPermissionTest {
-
- /**
- * Verify that create inet socket failed because of the permission is missing.
- * <p>Tests Permission:
- * {@link android.Manifest.permission#INTERNET}.
- */
- @SmallTest
- @Test
- public void testCreateSocket() throws Exception {
- try {
- Socket socket = new Socket("example.com", 80);
- fail("Ceate inet socket did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-}
diff --git a/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml b/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml
deleted file mode 100644
index 6babe8f..0000000
--- a/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.networkpermission.updatestatspermission.cts">
-
- <!--
- This CTS test is designed to test that an unprivileged app cannot get the
- UPDATE_DEVICE_STATS permission even if it specified it in the manifest. the
- UPDATE_DEVICE_STATS permission is a signature|privileged permission that CTS
- test cannot have.
- -->
- <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
- <uses-permission android:name="android.permission.INTERNET"/>
- <application>
- <uses-library android:name="android.test.runner"/>
- <activity android:name="android.networkpermission.updatestatspermission.cts.UpdateStatsPermissionTest"
- android:label="UpdateStatsPermissionTest"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
- </intent-filter>
- </activity>
- </application>
-
- <!--
- The CTS stubs package cannot be used as the target application here,
- since that requires many permissions to be set. Instead, specify this
- package itself as the target and include any stub activities needed.
-
- This test package uses the default InstrumentationTestRunner, because
- the InstrumentationCtsTestRunner is only available in the stubs
- package. That runner cannot be added to this package either, since it
- relies on hidden APIs.
- -->
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.networkpermission.updatestatspermission.cts"
- android:label="CTS tests for UPDATE_DEVICE_STATS permissions">
- <meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener"/>
- </instrumentation>
-
-</manifest>
diff --git a/tests/tests/netpermission/updatestatspermission/AndroidTest.xml b/tests/tests/netpermission/updatestatspermission/AndroidTest.xml
deleted file mode 100644
index c47cad9..0000000
--- a/tests/tests/netpermission/updatestatspermission/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<configuration description="Config for CTS update stats permission test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="networking" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <option name="not-shardable" value="true" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsNetTestCasesUpdateStatsPermission.apk" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.networkpermission.updatestatspermission.cts" />
- <option name="runtime-hint" value="10s" />
- </test>
-</configuration>
diff --git a/tests/tests/netpermission/updatestatspermission/TEST_MAPPING b/tests/tests/netpermission/updatestatspermission/TEST_MAPPING
deleted file mode 100644
index 6d6dfe0..0000000
--- a/tests/tests/netpermission/updatestatspermission/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsNetTestCasesUpdateStatsPermission"
- }
- ]
-}
diff --git a/tests/tests/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java b/tests/tests/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
deleted file mode 100644
index bea843c..0000000
--- a/tests/tests/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
+++ /dev/null
@@ -1,90 +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.net.cts.networkpermission.updatestatspermission;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.net.TrafficStats;
-import android.os.Process;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.OutputStream;
-import java.net.Socket;
-
-/**
-* Test that protected android.net.ConnectivityManager methods cannot be called without
-* permissions
-*/
-@RunWith(AndroidJUnit4.class)
-public class UpdateStatsPermissionTest {
-
- /**
- * Verify that setCounterSet for a different uid failed because of the permission cannot be
- * granted to a third-party app.
- * <p>Tests Permission:
- * {@link android.Manifest.permission#UPDATE_DEVICE_STATS}.
- */
- @SmallTest
- @Test
- public void testUpdateDeviceStatsPermission() throws Exception {
-
- // Set the current thread uid to a another uid. It should silently fail when tagging the
- // socket since the current process doesn't have UPDATE_DEVICE_STATS permission.
- TrafficStats.setThreadStatsTag(0);
- TrafficStats.setThreadStatsUid(/*root uid*/ 0);
- Socket socket = new Socket("example.com", 80);
- TrafficStats.tagSocket(socket);
-
- // Transfer 1K of data to a remote host and verify the stats is still billed to the current
- // uid.
- final int byteCount = 1024;
-
- socket.setTcpNoDelay(true);
- socket.setSoLinger(true, 0);
- OutputStream out = socket.getOutputStream();
- byte[] buf = new byte[byteCount];
- final long uidTxBytesBefore = TrafficStats.getUidTxBytes(Process.myUid());
- out.write(buf);
- out.close();
- socket.close();
- long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
- long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
- assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta="
- + uidTxDeltaBytes + " >= " + byteCount, uidTxDeltaBytes >= byteCount);
- }
-
- static final int UNSUPPORTED = -1;
-
- /**
- * Verify that get TrafficStats of a different uid failed because of the permission is not
- * granted to a third-party app.
- * <p>Tests Permission:
- * {@link android.Manifest.permission#UPDATE_DEVICE_STATS}.
- */
- @SmallTest
- @Test
- public void testGetStatsOfOtherUid() throws Exception {
- // Test get stats of another uid failed since the current process does not have permission
- assertEquals(UNSUPPORTED, TrafficStats.getUidRxBytes(/*root uid*/ 0));
- }
-}
diff --git a/tests/tests/neuralnetworks/Android.mk b/tests/tests/neuralnetworks/Android.mk
index 7ac51ea..d8b6ee3 100644
--- a/tests/tests/neuralnetworks/Android.mk
+++ b/tests/tests/neuralnetworks/Android.mk
@@ -43,4 +43,3 @@
include $(BUILD_CTS_EXECUTABLE)
include $(nnapi_cts_dir)/benchmark/Android.mk
-include $(nnapi_cts_dir)/tflite_delegate/Android.mk
diff --git a/tests/tests/neuralnetworks/tflite_delegate/Android.bp b/tests/tests/neuralnetworks/tflite_delegate/Android.bp
new file mode 100644
index 0000000..6acce1d
--- /dev/null
+++ b/tests/tests/neuralnetworks/tflite_delegate/Android.bp
@@ -0,0 +1,58 @@
+// 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 {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// The "CtsTfliteNnapiDelegateTests_static" has been moved to
+// external/tensorflow/Android.bp, due to the fact that the srcs files
+// are not in the current directory.
+
+// Build the actual CTS module with the static lib above.
+// This is necessary for the build system to pickup the AndroidTest.xml.
+
+cc_test {
+ name: "CtsTfliteNnapiDelegateTestCases",
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+ whole_static_libs: ["TfliteNnapiDelegateTests_static"],
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ "libneuralnetworks",
+ ],
+ static_libs: [
+ "libgtest_ndk_c++",
+ "libgmock_ndk",
+ "libtflite_static",
+ ],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "mts",
+ "mts-neuralnetworks",
+ "general-tests",
+ ],
+ sdk_version: "current",
+ stl: "c++_static",
+}
diff --git a/tests/tests/neuralnetworks/tflite_delegate/Android.mk b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
deleted file mode 100644
index 785f7bd..0000000
--- a/tests/tests/neuralnetworks/tflite_delegate/Android.mk
+++ /dev/null
@@ -1,80 +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.
-
-my_dir := $(call my-dir)
-
-LOCAL_PATH:= external/tensorflow/
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsTfliteNnapiDelegateTests_static
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SRC_FILES := \
- tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc \
- tensorflow/lite/kernels/test_util.cc \
- tensorflow/core/platform/default/logging.cc \
- tensorflow/core/platform/default/env_time.cc \
- tensorflow/lite/kernels/acceleration_test_util.cc \
- tensorflow/lite/kernels/acceleration_test_util_internal.cc \
- tensorflow/lite/delegates/nnapi/acceleration_test_list.cc \
- tensorflow/lite/delegates/nnapi/acceleration_test_util.cc
-LOCAL_CPP_EXTENSION := .cc
-
-LOCAL_C_INCLUDES += external/flatbuffers/include
-LOCAL_C_INCLUDES += external/tensorflow
-LOCAL_C_INCLUDES += external/ruy
-
-LOCAL_CFLAGS := \
- -DPLATFORM_POSIX_ANDROID \
- -Wall \
- -Werror \
- -Wextra \
- -Wno-extern-c-compat \
- -Wno-sign-compare \
- -Wno-unused-parameter \
- -Wno-unused-private-field \
-
-LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks
-LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgmock_ndk libtflite_static
-LOCAL_HEADER_LIBRARIES := libeigen gemmlowp_headers libtflite_schema_headers
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-include $(BUILD_STATIC_LIBRARY)
-
-
-# Build the actual CTS module with the static lib above.
-# This is necessary for the build system to pickup the AndroidTest.xml.
-LOCAL_PATH:= $(my_dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsTfliteNnapiDelegateTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_WHOLE_STATIC_LIBRARIES := CtsTfliteNnapiDelegateTests_static
-
-LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks
-LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgmock_ndk libtflite_static
-LOCAL_CTS_TEST_PACKAGE := android.neuralnetworks
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts mts mts-neuralnetworks general-tests
-
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java
index 0b5ed0d..54caf6b 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -77,6 +77,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -147,6 +148,63 @@
}
@Test
+ public void testThreadBuilder_detectUnbufferedIo() throws Exception {
+ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
+ .penaltyLog()
+ .detectUnbufferedIo()
+ .build();
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(policy).build());
+
+ final File test = File.createTempFile("foo", "bar");
+ inspectViolation(
+ () -> {
+ writeUnbuffered(test);
+ },
+ info -> {
+ assertThat(info.getViolationDetails()).isNull();
+ assertThat(info.getStackTrace()).contains("UnbufferedIoViolation");
+ });
+ }
+
+ @Test
+ public void testThreadBuilder_permitUnbufferedIo() throws Exception {
+ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
+ .penaltyLog()
+ .permitUnbufferedIo()
+ .build();
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(policy).build());
+
+ final File test = File.createTempFile("foo", "bar");
+ inspectViolation(
+ () -> {
+ writeUnbuffered(test);
+ },
+ info -> {
+ assertThat(info).isNull();
+ });
+ }
+
+ private void writeUnbuffered(File file) throws Exception {
+ if (file.exists()) {
+ file.delete();
+ }
+
+ try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
+ for (int i = 0; i < 11; i++) {
+ out.write(1);
+ out.write(2);
+ out.write(3);
+ out.write(4);
+ out.flush();
+ }
+ } finally {
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+ }
+
+ @Test
public void testUnclosedCloseable() throws Exception {
//clean before test
System.gc();
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index 3d91b81..e249fe3 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -47,11 +47,13 @@
":CtsUsePermissionApp30",
":CtsUsePermissionApp30WithBackground",
":CtsUsePermissionApp30WithBluetooth",
+ ":CtsUsePermissionApp31",
":CtsUsePermissionAppLatest",
":CtsUsePermissionAppLatestNone",
":CtsUsePermissionAppWithOverlay",
":CtsAccessMicrophoneApp",
":CtsAccessMicrophoneApp2",
+ ":CtsAccessMicrophoneAppLocationProvider",
],
test_suites: [
"cts",
diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml
index d708d76..2342b6a 100644
--- a/tests/tests/permission3/AndroidTest.xml
+++ b/tests/tests/permission3/AndroidTest.xml
@@ -41,6 +41,7 @@
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="push" value="CtsAccessMicrophoneApp.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneApp.apk" />
<option name="push" value="CtsAccessMicrophoneApp2.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneApp2.apk" />
+ <option name="push" value="CtsAccessMicrophoneAppLocationProvider.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneAppLocationProvider.apk" />
<option name="push" value="CtsPermissionPolicyApp25.apk->/data/local/tmp/cts/permission3/CtsPermissionPolicyApp25.apk" />
<option name="push" value="CtsUsePermissionApp22.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22.apk" />
<option name="push" value="CtsUsePermissionApp22CalendarOnly.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22CalendarOnly.apk" />
@@ -53,6 +54,7 @@
<option name="push" value="CtsUsePermissionApp30.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30.apk" />
<option name="push" value="CtsUsePermissionApp30WithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBackground.apk" />
<option name="push" value="CtsUsePermissionApp30WithBluetooth.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBluetooth.apk" />
+ <option name="push" value="CtsUsePermissionApp31.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp31.apk" />
<option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatest.apk" />
<option name="push" value="CtsUsePermissionAppLatestNone.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestNone.apk" />
<option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppWithOverlay.apk" />
diff --git a/tests/tests/netpermission/internetpermission/Android.bp b/tests/tests/permission3/UsePermissionApp31/Android.bp
similarity index 66%
rename from tests/tests/netpermission/internetpermission/Android.bp
rename to tests/tests/permission3/UsePermissionApp31/Android.bp
index 37ad7cb..48a2d4f 100644
--- a/tests/tests/netpermission/internetpermission/Android.bp
+++ b/tests/tests/permission3/UsePermissionApp31/Android.bp
@@ -1,4 +1,5 @@
-// Copyright (C) 2019 The Android Open Source Project
+//
+// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,23 +12,21 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+//
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test {
- name: "CtsNetTestCasesInternetPermission",
- defaults: ["cts_defaults"],
-
- srcs: ["src/**/*.java"],
-
- static_libs: ["ctstestrunner-axt"],
-
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
+android_test_helper_app {
+ name: "CtsUsePermissionApp31",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
],
-
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+ certificate: ":cts-testkey2",
+ target_sdk_version: "31",
+ min_sdk_version: "31",
}
diff --git a/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml
new file mode 100644
index 0000000..f6d9b29
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission3.cts.usepermission">
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.CAMERA" />
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/tests/netpermission/updatestatspermission/Android.bp b/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp
similarity index 65%
copy from tests/tests/netpermission/updatestatspermission/Android.bp
copy to tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp
index 7a24886..a2cd88d 100644
--- a/tests/tests/netpermission/updatestatspermission/Android.bp
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp
@@ -1,4 +1,5 @@
-// Copyright (C) 2019 The Android Open Source Project
+//
+// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,23 +12,20 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+//
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test {
- name: "CtsNetTestCasesUpdateStatsPermission",
- defaults: ["cts_defaults"],
-
- srcs: ["src/**/*.java"],
-
- static_libs: ["ctstestrunner-axt"],
-
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
+android_test_helper_app {
+ name: "CtsAccessMicrophoneAppLocationProvider",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "31",
+ srcs: [
+ "src/**/*.kt",
],
-
-}
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml
new file mode 100644
index 0000000..56afc09
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission3.cts.accessmicrophoneapplocationprovider">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <attribution
+ android:label="@string/attribution_label"
+ android:tag="test.tag" />
+
+ <application
+ android:attributionsAreUserVisible="true"
+ android:label="LocationProviderWithMicApp">
+ <activity android:name=".AddLocationProviderActivity" android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".UseMicrophoneActivity" android:exported="true"/>
+ </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml b/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml
new file mode 100644
index 0000000..9682d7f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <string name="attribution_label">Attribution Label</string>
+</resources>
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
new file mode 100644
index 0000000..3b3355cc
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.permission3.cts.accessmicrophoneapplocationprovider
+
+import android.app.Activity
+import android.location.Criteria
+import android.location.LocationManager
+import android.os.Bundle
+
+/**
+ * An activity that adds this package as a test location provider and uses microphone.
+ */
+class AddLocationProviderActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val attrContext = createAttributionContext("test.tag")
+ val locationManager = attrContext.getSystemService(LocationManager::class.java)
+ locationManager.addTestProvider(
+ packageName, false, false, false, false, false, false, false, Criteria.POWER_LOW,
+ Criteria.ACCURACY_COARSE
+ )
+
+ setResult(RESULT_OK)
+ finish()
+ }
+}
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
new file mode 100644
index 0000000..0d8374f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.permission3.cts.accessmicrophoneapplocationprovider
+
+import android.app.Activity
+import android.content.Context
+import android.media.AudioFormat
+import android.media.AudioRecord
+import android.media.MediaRecorder
+import android.os.Bundle
+import android.os.Handler
+
+private const val USE_DURATION_MS = 10000L
+private const val SAMPLE_RATE_HZ = 44100
+
+/**
+ * An activity that uses microphone.
+ */
+class UseMicrophoneActivity : Activity() {
+ private var recorder: AudioRecord? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val attrContext = createAttributionContext("test.tag")
+ useMic(attrContext)
+ setResult(RESULT_OK)
+ finish()
+ }
+
+ override fun finish() {
+ recorder?.stop()
+ recorder = null
+ super.finish()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ finish()
+ }
+
+ private fun useMic(context: Context) {
+ recorder = AudioRecord.Builder()
+ .setAudioSource(MediaRecorder.AudioSource.MIC)
+ .setAudioFormat(AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setSampleRate(SAMPLE_RATE_HZ)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .build())
+ .setContext(context)
+ .build()
+ recorder?.startRecording()
+ Handler().postDelayed({ finish() }, USE_DURATION_MS)
+ }
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt
new file mode 100644
index 0000000..014a4f3
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.content.Intent
+import com.android.compatibility.common.util.SystemUtil
+
+/** Base class for the permission hub tests. */
+abstract class BasePermissionHubTest : BasePermissionTest() {
+
+ protected fun openMicrophoneTimeline() {
+ SystemUtil.runWithShellPermissionIdentity {
+ context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_HISTORY).apply {
+ putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME,
+ android.Manifest.permission_group.MICROPHONE)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
index d968dce..49646f4 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
@@ -59,6 +59,10 @@
private val mPermissionControllerResources: Resources = context.createPackageContext(
context.packageManager.permissionControllerPackageName, 0).resources
+ protected val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ protected val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+ protected val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+
@get:Rule
val disableAnimationRule = DisableAnimationRule()
@@ -154,6 +158,10 @@
waitForIdle()
}
+ protected fun clickPermissionControllerUi(selector: BySelector, timeoutMillis: Long = 20_000) {
+ click(selector.pkg(context.packageManager.permissionControllerPackageName), timeoutMillis)
+ }
+
protected fun pressBack() {
uiDevice.pressBack()
waitForIdle()
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index ab4a05a..6c69a65 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -52,6 +52,8 @@
const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk"
const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk"
const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk"
+ const val APP_APK_PATH_31 = "$APK_DIRECTORY/CtsUsePermissionApp31.apk"
+
const val APP_APK_PATH_30_WITH_BACKGROUND =
"$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk"
const val APP_APK_PATH_30_WITH_BLUETOOTH =
@@ -74,6 +76,8 @@
"com.android.permissioncontroller:" +
"id/permission_no_upgrade_and_dont_ask_again_button"
+ const val ALLOW_ALWAYS_RADIO_BUTTON =
+ "com.android.permissioncontroller:id/allow_always_radio_button"
const val ALLOW_RADIO_BUTTON = "com.android.permissioncontroller:id/allow_radio_button"
const val ALLOW_FOREGROUND_RADIO_BUTTON =
"com.android.permissioncontroller:id/allow_foreground_only_radio_button"
@@ -97,10 +101,6 @@
DENIED_WITH_PREJUDICE
}
- protected val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
- protected val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
- protected val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
-
private val platformResources = context.createPackageContext("android", 0).resources
private val permissionToLabelResNameMap = mapOf(
// Contacts
@@ -130,6 +130,8 @@
to "@android:string/permgrouplab_location",
android.Manifest.permission.ACCESS_COARSE_LOCATION
to "@android:string/permgrouplab_location",
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+ to "@android:string/permgrouplab_location",
// Phone
android.Manifest.permission.READ_PHONE_STATE
to "@android:string/permgrouplab_phone",
@@ -449,7 +451,7 @@
if (isWatch) {
click(By.text(permissionLabel), 40_000)
} else {
- click(By.text(permissionLabel))
+ clickPermissionControllerUi(By.text(permissionLabel))
}
val wasGranted = if (isAutomotive) {
@@ -512,6 +514,8 @@
PermissionState.ALLOWED ->
if (showsForegroundOnlyButton(permission)) {
By.res(ALLOW_FOREGROUND_RADIO_BUTTON)
+ } else if (showsAlwaysButton(permission)) {
+ By.res(ALLOW_ALWAYS_RADIO_BUTTON)
} else if (isMediaStorageButton(permission, targetSdk)) {
// Uses "allow_foreground_only_radio_button" as id
byTextRes(R.string.allow_media_storage)
@@ -581,6 +585,12 @@
else -> false
}
+ private fun showsAlwaysButton(permission: String): Boolean =
+ when (permission) {
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true
+ else -> false
+ }
+
private fun isMediaStorageButton(permission: String, targetSdk: Int): Boolean =
if (isTv || isWatch) {
false
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
new file mode 100644
index 0000000..e88572d
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.app.Activity
+import android.app.AppOpsManager
+import android.content.ComponentName
+import android.content.Intent
+import android.location.LocationManager
+import android.os.Build
+import android.provider.DeviceConfig
+import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.AppOpsUtils.setOpMode
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.modules.utils.build.SdkLevel
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests permission attribution for location providers.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+class PermissionAttributionTest : BasePermissionHubTest() {
+ private val micLabel = packageManager.getPermissionGroupInfo(
+ android.Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+ val locationManager = context.getSystemService(LocationManager::class.java)!!
+ private var wasEnabled = false
+
+ // Permission history is not available on Auto devices running S or below.
+ @Before
+ fun assumeNotAutoBelowT() {
+ assumeFalse(isAutomotive && !SdkLevel.isAtLeastT())
+ }
+
+ @Before
+ fun installAppLocationProviderAndAllowMockLocation() {
+ installPackage(APP_APK_PATH, grantRuntimePermissions = true)
+ // The package name of a mock location provider is the caller adding it, so we have to let
+ // the test app add itself.
+ setOpMode(APP_PACKAGE_NAME, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpsManager.MODE_ALLOWED)
+ }
+
+ @Before
+ fun setup() {
+ // Allow ourselves to reliably remove the test location provider.
+ setOpMode(
+ context.packageName, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpsManager.MODE_ALLOWED
+ )
+ wasEnabled = setSubattributionEnabledStateIfNeeded(true)
+ }
+
+ @After
+ fun teardown() {
+ locationManager.removeTestProvider(APP_PACKAGE_NAME)
+ if (!wasEnabled) {
+ setSubattributionEnabledStateIfNeeded(false)
+ }
+ }
+
+ @Test
+ fun testLocationProviderAttributionForMicrophone() {
+ enableAppAsLocationProvider()
+ useMicrophone()
+ openMicrophoneTimeline()
+
+ waitFindObject(By.descContains(micLabel))
+ waitFindObject(By.textContains(APP_LABEL))
+ waitFindObject(By.textContains(ATTRIBUTION_LABEL))
+ }
+
+ private fun enableAppAsLocationProvider() {
+ // Add the test app as location provider.
+ val future = startActivityForFuture(
+ Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.AddLocationProviderActivity"
+ )
+ }
+ )
+ val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ assertEquals(Activity.RESULT_OK, result.resultCode)
+ assertTrue(
+ callWithShellPermissionIdentity {
+ locationManager.isProviderPackage(APP_PACKAGE_NAME)
+ }
+ )
+ }
+
+ private fun useMicrophone() {
+ val future = startActivityForFuture(
+ Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.UseMicrophoneActivity"
+ )
+ }
+ )
+ val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ assertEquals(Activity.RESULT_OK, result.resultCode)
+ }
+
+ private fun setSubattributionEnabledStateIfNeeded(shouldBeEnabled: Boolean): Boolean {
+ var currentlyEnabled = false
+ runWithShellPermissionIdentity {
+ currentlyEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ FLAG_SUBATTRIBUTION, false)
+ if (currentlyEnabled != shouldBeEnabled) {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, FLAG_SUBATTRIBUTION,
+ shouldBeEnabled.toString(), false)
+ }
+ }
+ return currentlyEnabled
+ }
+
+ companion object {
+ const val APP_APK_PATH = "$APK_DIRECTORY/CtsAccessMicrophoneAppLocationProvider.apk"
+ const val APP_PACKAGE_NAME = "android.permission3.cts.accessmicrophoneapplocationprovider"
+ const val APP_LABEL = "LocationProviderWithMicApp"
+ const val ATTRIBUTION_LABEL = "Attribution Label"
+ const val FLAG_SUBATTRIBUTION = "permissions_hub_subattribution_enabled"
+ }
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
index 794a731..db0b2b4 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
@@ -18,12 +18,11 @@
import android.Manifest
import android.content.Intent
-import android.content.pm.PackageManager.FEATURE_LEANBACK
-import android.content.pm.PackageManager.FEATURE_AUTOMOTIVE
import android.os.Build
import android.support.test.uiautomator.By
import androidx.test.filters.SdkSuppress
import com.android.compatibility.common.util.SystemUtil
+import com.android.modules.utils.build.SdkLevel
import org.junit.After
import org.junit.Assume.assumeFalse
import org.junit.Before
@@ -40,19 +39,19 @@
private const val MORE_OPTIONS = "More options"
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
-class PermissionHistoryTest : BasePermissionTest() {
+class PermissionHistoryTest : BasePermissionHubTest() {
private val micLabel = packageManager.getPermissionGroupInfo(
- Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+ Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
// Permission history is not available on TV devices.
@Before
- fun assumeNotTv() =
- assumeFalse(packageManager.hasSystemFeature(FEATURE_LEANBACK))
+ fun assumeNotTv() = assumeFalse(isTv)
- // Permission history is not available on Auto devices.
+ // Permission history is not available on Auto devices running S or below.
@Before
- fun assumeNotAuto() =
- assumeFalse(packageManager.hasSystemFeature(FEATURE_AUTOMOTIVE))
+ fun assumeNotAutoBelowT() {
+ assumeFalse(isAutomotive && !SdkLevel.isAtLeastT())
+ }
@Before
fun installApps() {
@@ -135,15 +134,6 @@
})
}
- private fun openMicrophoneTimeline() {
- SystemUtil.runWithShellPermissionIdentity {
- context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_HISTORY).apply {
- putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, Manifest.permission_group.MICROPHONE)
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- })
- }
- }
-
private fun openPermissionDashboard() {
SystemUtil.runWithShellPermissionIdentity {
context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).apply {
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
index 8925088..b26e536 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
@@ -20,7 +20,9 @@
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Point
+import android.os.Build
import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
import com.android.compatibility.common.util.SystemUtil
import org.junit.Assume.assumeFalse
import org.junit.Before
@@ -55,6 +57,7 @@
tryClicking(buttonCenter)
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
@Test
fun testTapjackGrantDialog_partialOverlay() {
// PermissionController for television uses a floating window.
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
index 26fe615..0a2c373 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
@@ -16,8 +16,10 @@
package android.permission3.cts
+import android.content.pm.PackageManager
import androidx.test.filters.FlakyTest
-import com.android.modules.utils.build.SdkLevel
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assert
import org.junit.Assume
import org.junit.Before
import org.junit.Test
@@ -182,11 +184,7 @@
// Request the permission and allow it
// Make sure the permission is granted
requestAppPermissionsAndAssertResult(android.Manifest.permission.CAMERA to true) {
- if (SdkLevel.isAtLeastS()) {
- clickPermissionRequestAllowForegroundButton()
- } else {
- clickPermissionRequestAllowButton()
- }
+ clickPermissionRequestAllowForegroundButton()
}
}
@@ -290,11 +288,7 @@
null to false,
android.Manifest.permission.RECORD_AUDIO to true
) {
- if (SdkLevel.isAtLeastS()) {
- clickPermissionRequestAllowForegroundButton()
- } else {
- clickPermissionRequestAllowButton()
- }
+ clickPermissionRequestAllowForegroundButton()
clickPermissionRequestAllowButton()
}
}
@@ -306,6 +300,34 @@
requestAppPermissionsAndAssertResult(INVALID_PERMISSION to false) {}
}
+ @Test
+ fun testAskButtonSetsFlags() {
+ Assume.assumeFalse("other form factors might not support the ask button",
+ isTv || isAutomotive || isWatch)
+
+ grantAppPermissions(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, targetSdk = 23)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, true)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, true)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, true)
+
+ revokeAppPermissions(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, targetSdk = 23)
+ SystemUtil.runWithShellPermissionIdentity {
+ val perms = listOf(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ for (perm in perms) {
+ var flags = packageManager.getPermissionFlags(perm, APP_PACKAGE_NAME, context.user)
+ Assert.assertEquals("USER_SET should not be set for $perm", 0,
+ flags and PackageManager.FLAG_PERMISSION_USER_SET)
+ Assert.assertEquals("USER_FIXED should not be set for $perm", 0,
+ flags and PackageManager.FLAG_PERMISSION_USER_FIXED)
+ Assert.assertEquals("ONE_TIME should be set for $perm",
+ PackageManager.FLAG_PERMISSION_ONE_TIME,
+ flags and PackageManager.FLAG_PERMISSION_ONE_TIME)
+ }
+ }
+ }
+
private fun denyPermissionRequestWithPrejudice() {
if (isTv || isWatch) {
clickPermissionRequestDontAskAgainButton()
diff --git a/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt
new file mode 100644
index 0000000..d39fb9c
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.content.Intent
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.location.LocationManager
+import android.Manifest.permission_group.CAMERA as CAMERA_PERMISSION_GROUP
+import android.Manifest.permission_group.LOCATION as LOCATION_PERMISSION_GROUP
+import android.Manifest.permission_group.MICROPHONE as MICROPHONE_PERMISSION_GROUP
+import android.os.Build
+import android.provider.DeviceConfig
+import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Banner card display tests on sensors being blocked
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+class SensorBlockedBannerTest : BaseUsePermissionTest() {
+ companion object {
+ const val LOCATION = -1
+ const val WARNING_BANNER_ENABLED = "warning_banner_enabled"
+ }
+
+ val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!!
+ val locationManager = context.getSystemService(LocationManager::class.java)!!
+ private val originalEnabledValue = callWithShellPermissionIdentity {
+ DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY,
+ WARNING_BANNER_ENABLED, false.toString())
+ }
+
+ private val sensorToPermissionGroup = mapOf(CAMERA to CAMERA_PERMISSION_GROUP,
+ MICROPHONE to MICROPHONE_PERMISSION_GROUP,
+ LOCATION to LOCATION_PERMISSION_GROUP)
+
+ private val permToTitle = mapOf(CAMERA to "blocked_camera_title",
+ MICROPHONE to "blocked_microphone_title",
+ LOCATION to "blocked_location_title")
+
+ @Before
+ fun setup() {
+ Assume.assumeFalse(isTv)
+ // TODO(b/203784852) Auto will eventually support the blocked sensor banner, but there won't
+ // be support in T or below
+ Assume.assumeFalse(isAutomotive)
+ installPackage(APP_APK_PATH_31)
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+ WARNING_BANNER_ENABLED, true.toString(), false)
+ }
+ }
+
+ @After
+ fun restoreWarningBannerState() {
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+ WARNING_BANNER_ENABLED, originalEnabledValue, false)
+ }
+ }
+
+ private fun navigateAndTest(sensor: Int) {
+ val permissionGroup = sensorToPermissionGroup.getOrDefault(sensor, "Break")
+ val intent = Intent(Intent.ACTION_MANAGE_PERMISSION_APPS)
+ .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permissionGroup)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ runWithShellPermissionIdentity {
+ context.startActivity(intent)
+ }
+ val bannerTitle = permToTitle.getOrDefault(sensor, "Break")
+ waitFindObject(By.text(getPermissionControllerString(bannerTitle)))
+ pressBack()
+ }
+
+ private fun runSensorTest(sensor: Int) {
+ val blocked = isSensorPrivacyEnabled(sensor)
+ if (!blocked) {
+ setSensor(sensor, true)
+ }
+ navigateAndTest(sensor)
+ if (!blocked) {
+ setSensor(sensor, false)
+ }
+ }
+
+ @Test
+ fun testCameraCardDisplayed() {
+ Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(CAMERA))
+ runSensorTest(CAMERA)
+ }
+
+ @Test
+ fun testMicCardDisplayed() {
+ Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(MICROPHONE))
+ runSensorTest(MICROPHONE)
+ }
+
+ @Test
+ fun testLocationCardDisplayed() {
+ runSensorTest(LOCATION)
+ }
+
+ private fun setSensor(sensor: Int, enable: Boolean) {
+ if (sensor == LOCATION) {
+ runWithShellPermissionIdentity {
+ locationManager.setLocationEnabledForUser(!enable,
+ android.os.Process.myUserHandle())
+ if (enable) {
+ try {
+ waitFindObjectOrNull(By.text("CLOSE"))?.click()
+ } catch (e: Exception) {
+ // Do nothing, warning didn't show up so test can proceed
+ }
+ }
+ }
+ } else {
+ runWithShellPermissionIdentity {
+ sensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sources.OTHER,
+ sensor, enable)
+ }
+ }
+ }
+
+ private fun isSensorPrivacyEnabled(sensor: Int): Boolean {
+ return if (sensor == LOCATION) {
+ callWithShellPermissionIdentity {
+ !locationManager.isLocationEnabled()
+ }
+ } else {
+ callWithShellPermissionIdentity {
+ sensorPrivacyManager.isSensorPrivacyEnabled(sensor)
+ }
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
index ff5a1c8..c0e100c 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
@@ -24,6 +24,7 @@
import android.provider.MediaStore.Audio.Media;
import android.provider.cts.ProviderTestUtils;
+import androidx.annotation.RequiresApi;
import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
@@ -91,7 +92,6 @@
public static final int IS_RINGTONE = 0;
public static final int IS_NOTIFICATION = 0;
public static final int IS_ALARM = 0;
- public static final int IS_RECORDING = 0;
public static final int IS_MUSIC = 1;
public static final int YEAR = 1992;
public static final int TRACK = 1;
@@ -135,7 +135,6 @@
values.put(Media.IS_MUSIC, IS_MUSIC);
values.put(Media.IS_ALARM, IS_ALARM);
values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
- values.put(Media.IS_RECORDING, IS_RECORDING);
values.put(Media.IS_RINGTONE, IS_RINGTONE);
return values;
}
@@ -278,6 +277,28 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ public static class Audio7 extends Audio1 {
+ public static final int IS_RECORDING = 0;
+
+ private Audio7() {
+ }
+
+ private static Audio7 sInstance = new Audio7();
+
+ public static Audio7 getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public ContentValues getContentValues(String volumeName) {
+ ContentValues values = super.getContentValues(volumeName);
+ values.put(Media.DATA, values.getAsString(Media.DATA) + ".recording.mp3");
+ values.put(Media.IS_RECORDING, IS_RECORDING);
+ return values;
+ }
+ }
+
@Test
public void testStub() {
// No-op test here to keep atest happy
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
index aed220d..c6768ab 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
@@ -221,8 +221,9 @@
Optional.of("Android/media/android.provider.cts/foo"), null));
assertFalse(updatePlacement(uri,
Optional.of("Android/media/com.example/foo"), null));
- assertFalse(updatePlacement(uri,
+ assertTrue(updatePlacement(uri,
Optional.of("DCIM"), null));
+ assertFalse(updatePlacement(uri, Optional.of("Android/media"), null));
}
@Test
@@ -232,12 +233,13 @@
final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
mExternalImages, "image/jpeg");
- assertFalse(updatePlacement(uri,
+ assertTrue(updatePlacement(uri,
Optional.of("Android/media/android.provider.cts/foo"), null));
assertFalse(updatePlacement(uri,
Optional.of("Android/media/com.example/foo"), null));
assertTrue(updatePlacement(uri,
Optional.of("DCIM"), null));
+ assertFalse(updatePlacement(uri, Optional.of("Android/media"), null));
}
@Test
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
index 49a33ad..da7f0fb 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
@@ -27,16 +27,16 @@
import android.provider.MediaStore.MediaColumns;
import android.text.format.DateUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SdkSuppress;
+
import org.junit.Test;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.util.Objects;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.SdkSuppress;
-
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
public class MediaStoreUtils {
@Test
@@ -118,6 +118,10 @@
}
}
+ public void setIsFavorite(@Nullable Boolean isFavorite) {
+ this.insertValues.put(MediaColumns.IS_FAVORITE, isFavorite);
+ }
+
/**
* Optionally set the Uri from where the file has been downloaded. This is used
* for files being added to {@link Downloads} table.
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
index a196c5a..6a4c199 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
@@ -36,10 +36,10 @@
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Audio.Media;
import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.MediaColumns;
import android.provider.cts.ProviderTestUtils;
import android.provider.cts.R;
import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio7;
import android.provider.cts.media.MediaStoreUtils.PendingParams;
import android.provider.cts.media.MediaStoreUtils.PendingSession;
import android.util.Log;
@@ -157,7 +157,6 @@
assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
assertEquals(Audio1.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
- assertEquals(Audio1.IS_RECORDING, c.getInt(c.getColumnIndex(Media.IS_RECORDING)));
assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Media.TRACK)));
assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Media.YEAR)));
String titleKey = c.getString(c.getColumnIndex(Media.TITLE_KEY));
@@ -186,6 +185,41 @@
}
}
+ /**
+ * The test case checks below behaviors:
+ * 1. The IS_RECORDING column is in Audio table in MediaStore's database since S OS. Insert with
+ * IS_RECORDING column doesn't fail and we can query the result with IS_RECORDING column.
+ * 2. Validate IS_RECORDING is correctly set for database row.
+ */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ public void testStoreNotAudioRecordingMedia() {
+ Audio7 audio7 = Audio7.getInstance();
+ ContentValues values = audio7.getContentValues(mVolumeName);
+ //insert
+ Uri mediaUri = Media.getContentUri(mVolumeName);
+ Uri uri = mContentResolver.insert(mediaUri, values);
+ assertNotNull(uri);
+
+ try (Cursor c = mContentResolver.query(uri, null, null, null, null)) {
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ long id = c.getLong(c.getColumnIndex(Media._ID));
+ assertTrue(id > 0);
+ String expected = audio7.getContentValues(mVolumeName).getAsString(Media.DATA);
+ assertEquals(expected, c.getString(c.getColumnIndex(Media.DATA)));
+ assertEquals(Audio7.MIME_TYPE, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+ assertEquals(Audio7.IS_ALARM, c.getInt(c.getColumnIndex(Media.IS_ALARM)));
+ assertEquals(Audio7.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
+ assertEquals(Audio7.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
+ assertEquals(Audio7.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
+ assertEquals(Audio7.IS_RECORDING, c.getInt(c.getColumnIndex(Media.IS_RECORDING)));
+ } finally {
+ // delete
+ mContentResolver.delete(uri, null, null);
+ }
+ }
+
@Test(timeout = 60000)
public void testCanonicalize() throws Exception {
// Remove all audio left over from other tests
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java
index 72550c4..d86ffb1 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java
@@ -16,6 +16,11 @@
package android.view.textclassifier.cts;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.textclassifier.SelectionEvent;
+import android.view.textclassifier.TextClassifier;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -27,7 +32,23 @@
public class SelectionEventTest {
@Test
- public void testSelectionEvent_placeholder() {
- // TODO: add tests for SelectionEvent
+ public void testSelectionEvent() {
+ SelectionEvent event = SelectionEvent.createSelectionActionEvent(0, 1,
+ SelectionEvent.ACTION_COPY);
+ assertThat(event.getEventType()).isEqualTo(SelectionEvent.ACTION_COPY);
+ assertThat(event.getStart()).isEqualTo(0);
+ assertThat(event.getEnd()).isEqualTo(0);
+ assertThat(event.getInvocationMethod()).isEqualTo(SelectionEvent.INVOCATION_UNKNOWN);
+ assertThat(event.getEntityType()).isEqualTo(TextClassifier.TYPE_UNKNOWN);
+ assertThat(event.getEventIndex()).isEqualTo(0);
+ assertThat(event.getPackageName()).isEqualTo("");
+ assertThat(event.getSmartStart()).isEqualTo(0);
+ assertThat(event.getSmartEnd()).isEqualTo(0);
+ assertThat(event.getWidgetType()).isEqualTo(TextClassifier.WIDGET_TYPE_UNKNOWN);
+ assertThat(event.getWidgetVersion()).isNull();
+ assertThat(event.getResultId()).isEqualTo("");
+ assertThat(event.getEventTime()).isEqualTo(0);
+ assertThat(event.getDurationSinceSessionStart()).isEqualTo(0);
+ assertThat(event.getDurationSincePreviousEvent()).isEqualTo(0);
}
}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java
index 1414ed7..3592c96 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java
@@ -231,6 +231,7 @@
assertNull(request.getDefaultLocales());
assertTrue(request.getExtras().isEmpty());
assertNull(request.getEntityConfig());
+ assertNull(request.getCallingPackageName());
}
@Test
@@ -253,6 +254,7 @@
TextClassifier.HINT_TEXT_IS_EDITABLE,
request.getEntityConfig().getHints().iterator().next());
assertEquals(referenceTime, request.getReferenceTime());
+ assertNull(request.getCallingPackageName());
}
}
diff --git a/tests/tests/wifi/AndroidTest.xml b/tests/tests/wifi/AndroidTest.xml
index 421a9f7..5da40ea 100644
--- a/tests/tests/wifi/AndroidTest.xml
+++ b/tests/tests/wifi/AndroidTest.xml
@@ -43,6 +43,5 @@
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
<option name="mainline-module-package-name" value="com.google.android.wifi" />
- <option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
</configuration>
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
index 7130051..88457ca 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
@@ -259,7 +259,9 @@
* Tests the concurrent connection flow.
* 1. Connect to a network using peer to peer API.
* 2. Connect to a network using internet connectivity API.
- * 3. Verify that both connections are active.
+ * 3. Verify that both connections are active only the network for peer-to-peer and network
+ * for internet have different SSIDs. If they have the same SSID, verify there's exactly one
+ * connection.
*/
@Test
public void testConnectToInternetNetworkWhenConnectedToPeerPeerNetwork() throws Exception {
@@ -275,7 +277,9 @@
mTestNetworkForInternetConnection);
// Ensure that there are 2 wifi connections available for apps.
- assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
+ assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(
+ mTestNetworkForPeerToPeer.SSID.equals(mTestNetworkForInternetConnection.SSID)
+ ? 1 : 2);
}
/**
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
index 98ab11d..c845138 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
@@ -146,7 +146,7 @@
wifiManager.unregisterScanResultsCallback(scanResultsCallback);
}
List<ScanResult> scanResults = wifiManager.getScanResults();
- if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+ if (scanResults == null || scanResults.isEmpty()) continue;
for (ScanResult scanResult : scanResults) {
WifiConfiguration matchingNetwork = savedNetworks.stream()
.filter(network -> TextUtils.equals(
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
index 81129ed..ef52bc7 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -165,6 +165,7 @@
private static final int SCAN_TEST_WAIT_DURATION_MS = 15_000;
private static final int TEST_WAIT_DURATION_MS = 10_000;
private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
+ private static final int WIFI_PNO_CONNECT_TIMEOUT_MILLIS = 90_000;
private static final int WAIT_MSEC = 60;
private static final int DURATION_SCREEN_TOGGLE = 2000;
private static final int DURATION_SETTINGS_TOGGLE = 1_000;
@@ -439,6 +440,10 @@
}
}
+ private void waitForConnection(int timeoutMillis) throws Exception {
+ waitForNetworkInfoState(NetworkInfo.State.CONNECTED, timeoutMillis);
+ }
+
private void waitForConnection() throws Exception {
waitForNetworkInfoState(NetworkInfo.State.CONNECTED, WIFI_CONNECT_TIMEOUT_MILLIS);
}
@@ -1332,14 +1337,15 @@
// Skip the test if wifi module version is older than S.
return;
}
- List<WifiConfiguration> testConfigs = new ArrayList<>();
- testConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN));
- testConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE));
- testConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK));
- testConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE));
- testConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
+ List<WifiConfiguration> baseConfigs = new ArrayList<>();
+ baseConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN));
+ baseConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK));
+ baseConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
WifiConfiguration.SECURITY_TYPE_EAP));
- testConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
+ List<WifiConfiguration> upgradeConfigs = new ArrayList<>();
+ upgradeConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE));
+ upgradeConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE));
+ upgradeConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
@@ -1349,46 +1355,51 @@
mWifiManager.getPrivilegedConfiguredNetworks().size();
final int originalCallerConfiguredNetworksNumber =
mWifiManager.getCallerConfiguredNetworks().size();
- for (WifiConfiguration c: testConfigs) {
+ for (WifiConfiguration c: baseConfigs) {
WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c);
assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
assertTrue(result.networkId >= 0);
c.networkId = result.networkId;
}
- List<WifiConfiguration> expectedConfigs = testConfigs;
+ for (WifiConfiguration c: upgradeConfigs) {
+ WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c);
+ assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
+ assertTrue(result.networkId >= 0);
+ c.networkId = result.networkId;
+ }
+ // open/owe, psk/sae, and wpa2e/wpa3e should be merged
+ // so they should have the same network ID.
+ for (int i = 0; i < baseConfigs.size(); i++) {
+ assertEquals(baseConfigs.get(i).networkId, upgradeConfigs.get(i).networkId);
+ }
+
+ int numAddedConfigs = baseConfigs.size();
+ List<WifiConfiguration> expectedConfigs = new ArrayList<>(baseConfigs);
if (SdkLevel.isAtLeastS()) {
- // open/owe, psk/sae, and wpa2e/wpa3e should be merged
- // so they should have the same network ID.
- assertEquals(testConfigs.get(0).networkId, testConfigs.get(1).networkId);
- assertEquals(testConfigs.get(2).networkId, testConfigs.get(3).networkId);
- assertEquals(testConfigs.get(4).networkId, testConfigs.get(5).networkId);
- } else {
- // Network IDs for different security types should be unique for R
- assertNotEquals(testConfigs.get(0).networkId, testConfigs.get(1).networkId);
- assertNotEquals(testConfigs.get(2).networkId, testConfigs.get(3).networkId);
- assertNotEquals(testConfigs.get(4).networkId, testConfigs.get(5).networkId);
- // WPA3-Enterprise is omitted when WPA2-Enterprise is present for R
- expectedConfigs = testConfigs.subList(0, 5);
+ // S devices and above will return one additional config per each security type
+ // added, so we include the number of both base and upgrade configs.
+ numAddedConfigs += upgradeConfigs.size();
+ expectedConfigs.addAll(upgradeConfigs);
}
List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
- assertEquals(originalConfiguredNetworksNumber + expectedConfigs.size(),
+ assertEquals(originalConfiguredNetworksNumber + numAddedConfigs,
configuredNetworks.size());
assertConfigsAreFound(expectedConfigs, configuredNetworks);
List<WifiConfiguration> privilegedConfiguredNetworks =
mWifiManager.getPrivilegedConfiguredNetworks();
- assertEquals(originalPrivilegedConfiguredNetworksNumber + expectedConfigs.size(),
+ assertEquals(originalPrivilegedConfiguredNetworksNumber + numAddedConfigs,
privilegedConfiguredNetworks.size());
assertConfigsAreFound(expectedConfigs, privilegedConfiguredNetworks);
List<WifiConfiguration> callerConfiguredNetworks =
mWifiManager.getCallerConfiguredNetworks();
- assertEquals(originalCallerConfiguredNetworksNumber + expectedConfigs.size(),
+ assertEquals(originalCallerConfiguredNetworksNumber + numAddedConfigs,
callerConfiguredNetworks.size());
assertConfigsAreFound(expectedConfigs, callerConfiguredNetworks);
} finally {
- for (WifiConfiguration c: testConfigs) {
+ for (WifiConfiguration c: baseConfigs) {
if (c.networkId >= 0) {
mWifiManager.removeNetwork(c.networkId);
}
@@ -2276,6 +2287,7 @@
* configuration.
* @throws Exception
*/
+ @VirtualDeviceNotSupported
public void testSetGetSoftApConfigurationAndSoftApCapabilityCallback() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
@@ -2365,6 +2377,7 @@
* Verify that startTetheredHotspot with specific channel config.
* @throws Exception
*/
+ @VirtualDeviceNotSupported
public void testStartTetheredHotspotWithChannelConfigAndSoftApStateAndInfoCallback()
throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
@@ -2540,17 +2553,8 @@
assertTrue(actionListener.onSuccessCalled);
// Wait for connection to complete & ensure we are connected to the saved network.
waitForConnection();
- if (SdkLevel.isAtLeastS()) {
- assertEquals(savedNetworkToConnect.networkId,
- mWifiManager.getConnectionInfo().getNetworkId());
- } else {
- // In R, auto-upgraded network IDs may be different from the original saved network.
- // Since we may end up selecting the auto-upgraded network ID for connection and end
- // up connected to the original saved network with a different network ID, we should
- // instead match by SSID.
- assertEquals(savedNetworkToConnect.SSID,
- mWifiManager.getConnectionInfo().getSSID());
- }
+ assertEquals(savedNetworkToConnect.networkId,
+ mWifiManager.getConnectionInfo().getNetworkId());
} finally {
// Re-enable all saved networks before exiting.
if (savedNetworks != null) {
@@ -2865,7 +2869,7 @@
}
// make sure we're connected
- waitForConnection();
+ waitForConnection(WIFI_PNO_CONNECT_TIMEOUT_MILLIS);
WifiInfo currentNetwork = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getConnectionInfo);
diff --git a/tools/cts-dynamic-config/Android.mk b/tools/cts-dynamic-config/Android.mk
index fae519c..7a3b3c6 100644
--- a/tools/cts-dynamic-config/Android.mk
+++ b/tools/cts-dynamic-config/Android.mk
@@ -22,7 +22,7 @@
LOCAL_MODULE_CLASS := FAKE
LOCAL_IS_HOST_MODULE := true
-LOCAL_COMPATIBILITY_SUITE := cts general-tests mts
+LOCAL_COMPATIBILITY_SUITE := cts general-tests mts mts-media
# my_test_config_file := DynamicConfig.xml
# TODO (sbasi): Update to use BUILD_HOST_TEST_CONFIG when it's primary install