Merge "Support update and replace on conflict" into rvc-dev
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index 5bd8c80..c1849ab 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -33,7 +33,6 @@
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages;
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage;
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteVideo;
-import static com.android.providers.media.util.PermissionUtils.generateAppOpMessage;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -61,7 +60,6 @@
public final String packageNameUnchecked;
// Info used for logging permission checks
public @Nullable String attributionTag;
- private @Nullable String opDescription;
private LocalCallingIdentity(Context context, int pid, int uid, String packageNameUnchecked,
@Nullable String attributionTag) {
@@ -70,7 +68,6 @@
this.uid = uid;
this.packageNameUnchecked = packageNameUnchecked;
this.attributionTag = attributionTag;
- this.opDescription = null;
}
/**
@@ -216,7 +213,7 @@
public boolean hasPermission(int permission) {
if ((hasPermissionResolved & permission) == 0) {
- if (hasPermissionInternal(permission, opDescription)) {
+ if (hasPermissionInternal(permission)) {
hasPermission |= permission;
}
hasPermissionResolved |= permission;
@@ -224,7 +221,7 @@
return (hasPermission & permission) != 0;
}
- private boolean hasPermissionInternal(int permission, @Nullable String description) {
+ private boolean hasPermissionInternal(int permission) {
// While we're here, enforce any broad user-level restrictions
if ((uid == Process.SHELL_UID) && context.getSystemService(UserManager.class)
.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
@@ -246,26 +243,24 @@
case PERMISSION_IS_REDACTION_NEEDED:
return isRedactionNeededInternal();
case PERMISSION_READ_AUDIO:
- return checkPermissionReadAudio(context, pid, uid, getPackageName(), attributionTag,
- generateAppOpMessage(packageName, description));
+ return checkPermissionReadAudio(context, pid, uid, getPackageName(), attributionTag);
case PERMISSION_READ_VIDEO:
- return checkPermissionReadVideo(context, pid, uid, getPackageName(), attributionTag,
- generateAppOpMessage(packageName, description));
+ return checkPermissionReadVideo(context, pid, uid, getPackageName(), attributionTag);
case PERMISSION_READ_IMAGES:
- return checkPermissionReadImages(context, pid, uid, getPackageName(),
- attributionTag, generateAppOpMessage(packageName, description));
+ return checkPermissionReadImages(
+ context, pid, uid, getPackageName(), attributionTag);
case PERMISSION_WRITE_AUDIO:
- return checkPermissionWriteAudio(context, pid, uid, getPackageName(),
- attributionTag, generateAppOpMessage(packageName, description));
+ return checkPermissionWriteAudio(
+ context, pid, uid, getPackageName(), attributionTag);
case PERMISSION_WRITE_VIDEO:
- return checkPermissionWriteVideo(context, pid, uid, getPackageName(),
- attributionTag, generateAppOpMessage(packageName, description));
+ return checkPermissionWriteVideo(
+ context, pid, uid, getPackageName(), attributionTag);
case PERMISSION_WRITE_IMAGES:
- return checkPermissionWriteImages(context, pid, uid, getPackageName(),
- attributionTag, generateAppOpMessage(packageName, description));
+ return checkPermissionWriteImages(
+ context, pid, uid, getPackageName(), attributionTag);
case PERMISSION_MANAGE_EXTERNAL_STORAGE:
- return checkPermissionManageExternalStorage(context, pid, uid, getPackageName(),
- attributionTag, generateAppOpMessage(packageName, description));
+ return checkPermissionManageExternalStorage(
+ context, pid, uid, getPackageName(), attributionTag);
default:
return false;
}
@@ -309,14 +304,12 @@
private boolean isLegacyWriteInternal() {
return hasPermission(PERMISSION_IS_LEGACY_GRANTED)
- && checkPermissionWriteStorage(context, pid, uid, getPackageName(), attributionTag,
- /*opMessage*/ null);
+ && checkPermissionWriteStorage(context, pid, uid, getPackageName(), attributionTag);
}
private boolean isLegacyReadInternal() {
return hasPermission(PERMISSION_IS_LEGACY_GRANTED)
- && checkPermissionReadStorage(context, pid, uid, getPackageName(), attributionTag,
- /*opMessage*/ null);
+ && checkPermissionReadStorage(context, pid, uid, getPackageName(), attributionTag);
}
/** System internals or callers holding permission have no redaction */
@@ -371,8 +364,4 @@
public long getDeletedRowId(@NonNull String path) {
return rowIdOfDeletedPaths.getOrDefault(path, UNKNOWN_ROW_ID);
}
-
- public void setOpDescription(@Nullable String opDescription) {
- this.opDescription = opDescription;
- }
}
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 031a4fb..cbdff13 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -66,7 +66,6 @@
import static com.android.providers.media.util.Logging.LOGV;
import static com.android.providers.media.util.Logging.TAG;
import static com.android.providers.media.util.PermissionUtils.checkPermissionManageExternalStorage;
-import static com.android.providers.media.util.PermissionUtils.generateAppOpMessage;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpActiveChangedListener;
@@ -179,6 +178,7 @@
import com.android.providers.media.util.LongArray;
import com.android.providers.media.util.Metrics;
import com.android.providers.media.util.MimeUtils;
+import com.android.providers.media.util.PermissionUtils;
import com.android.providers.media.util.RedactingFileDescriptor;
import com.android.providers.media.util.SQLiteQueryBuilder;
import com.android.providers.media.util.XmpInterface;
@@ -411,15 +411,14 @@
* Retrieves a cached calling identity or creates a new one. Also, always sets the app-op
* description for the calling identity.
*/
- private LocalCallingIdentity getCachedCallingIdentityForFuse(
- int uid, @Nullable String opDescription) {
+ private LocalCallingIdentity getCachedCallingIdentityForFuse(int uid) {
synchronized (mCachedCallingIdentityForFuse) {
+ PermissionUtils.setOpDescription("via FUSE");
LocalCallingIdentity ident = mCachedCallingIdentityForFuse.get(uid);
if (ident == null) {
ident = LocalCallingIdentity.fromExternal(getContext(), uid);
mCachedCallingIdentityForFuse.put(uid, ident);
}
- ident.setOpDescription(opDescription);
return ident;
}
}
@@ -431,6 +430,7 @@
*/
private final ThreadLocal<LocalCallingIdentity> mCallingIdentity = ThreadLocal
.withInitial(() -> {
+ PermissionUtils.setOpDescription("via MediaProvider");
synchronized (mCachedCallingIdentity) {
final LocalCallingIdentity cached = mCachedCallingIdentity
.get(Binder.getCallingUid());
@@ -1065,9 +1065,13 @@
* to clear other apps' cache directories.
*/
static boolean hasPermissionToClearCaches(Context context, ApplicationInfo ai) {
- final String opMessage = generateAppOpMessage(ai.packageName, "clear app cache");
- return checkPermissionManageExternalStorage(context, /*pid*/ -1, ai.uid, ai.packageName,
- /*attributionTag*/ null, opMessage);
+ PermissionUtils.setOpDescription("clear app cache");
+ try {
+ return checkPermissionManageExternalStorage(context, /*pid*/ -1, ai.uid, ai.packageName,
+ /*attributionTag*/ null);
+ } finally {
+ PermissionUtils.clearOpDescription();
+ }
}
/**
@@ -1285,7 +1289,7 @@
@Keep
public String[] getFilesInDirectoryForFuse(String path, int uid) {
final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid, "readdir " + path));
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
try {
if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -1772,8 +1776,8 @@
@Keep
public int renameForFuse(String oldPath, String newPath, int uid) {
final String errorMessage = "Rename " + oldPath + " to " + newPath + " failed. ";
- final LocalCallingIdentity token = clearLocalCallingIdentity(
- getCachedCallingIdentityForFuse(uid, "rename " + oldPath + " to " + newPath));
+ final LocalCallingIdentity token =
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
try {
if (isPrivatePackagePathNotOwnedByCaller(oldPath)
@@ -1855,9 +1859,8 @@
@Override
public int checkUriPermission(@NonNull Uri uri, int uid,
/* @Intent.AccessUriMode */ int modeFlags) {
- final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid,
- /*opDescription*/ null));
+ final LocalCallingIdentity token = clearLocalCallingIdentity(
+ LocalCallingIdentity.fromExternal(getContext(), uid));
try {
final boolean allowHidden = isCallingPackageAllowedHidden();
@@ -5896,8 +5899,8 @@
return getRedactionRanges(file).redactionRanges;
}
- final LocalCallingIdentity token = clearLocalCallingIdentity(
- getCachedCallingIdentityForFuse(uid, "read metadata from " + path));
+ final LocalCallingIdentity token =
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
long[] res = new long[0];
try {
@@ -6017,8 +6020,8 @@
*/
@Keep
public int isOpenAllowedForFuse(String path, int uid, boolean forWrite) {
- final LocalCallingIdentity token = clearLocalCallingIdentity(
- getCachedCallingIdentityForFuse(uid, (forWrite ? "write " : "read ") + path));
+ final LocalCallingIdentity token =
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
try {
if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6210,7 +6213,7 @@
@Keep
public int insertFileIfNecessaryForFuse(@NonNull String path, int uid) {
final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid, "create " + path));
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
try {
if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6280,8 +6283,7 @@
@Keep
public int deleteFileForFuse(@NonNull String path, int uid) throws IOException {
final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid, "delete " + path));
-
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
try {
if (isPrivatePackagePathNotOwnedByCaller(path)) {
Log.e(TAG, "Can't delete a file in another app's external directory!");
@@ -6347,8 +6349,8 @@
@Keep
public int isDirectoryCreationOrDeletionAllowedForFuse(
@NonNull String path, int uid, boolean forCreate) {
- final LocalCallingIdentity token = clearLocalCallingIdentity(
- getCachedCallingIdentityForFuse(uid, (forCreate ? "mkdir " : "rmdir ") + path));
+ final LocalCallingIdentity token =
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
try {
// App dirs are not indexed, so we don't create an entry for the file.
@@ -6400,8 +6402,8 @@
*/
@Keep
public int isOpendirAllowedForFuse(@NonNull String path, int uid) {
- final LocalCallingIdentity token = clearLocalCallingIdentity(
- getCachedCallingIdentityForFuse(uid, "directory access " + path));
+ final LocalCallingIdentity token =
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
try {
if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6427,8 +6429,7 @@
@Keep
public boolean isUidForPackageForFuse(@NonNull String packageName, int uid) {
final LocalCallingIdentity token =
- clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid,
- /*opDescription*/ null));
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
try {
return isCallingIdentitySharedPackageName(packageName);
} finally {
diff --git a/src/com/android/providers/media/util/PermissionUtils.java b/src/com/android/providers/media/util/PermissionUtils.java
index e09feca..f277681 100644
--- a/src/com/android/providers/media/util/PermissionUtils.java
+++ b/src/com/android/providers/media/util/PermissionUtils.java
@@ -30,12 +30,13 @@
import static android.app.AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.provider.MediaStore;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
public class PermissionUtils {
// Callers must hold both the old and new permissions, so that we can
// handle obscure cases like when an app targets Q but was installed on
@@ -43,6 +44,14 @@
private static volatile int sLegacyMediaProviderUid = -1;
+ private static ThreadLocal<String> sOpDescription = new ThreadLocal<>();
+
+ public static void setOpDescription(@Nullable String description) {
+ sOpDescription.set(description);
+ }
+
+ public static void clearOpDescription() { sOpDescription.set(null); }
+
public static boolean checkPermissionSystem(
@NonNull Context context, int pid, int uid, String packageName) {
// Apps sharing legacy MediaProvider's uid like DownloadProvider and MTP are treated as
@@ -57,24 +66,21 @@
}
public static boolean checkPermissionManageExternalStorage(@NonNull Context context, int pid,
- int uid, @NonNull String packageName, @Nullable String attributionTag,
- @Nullable String opMessage) {
+ int uid, @NonNull String packageName, @Nullable String attributionTag) {
return noteAppOpPermission(context, pid, uid, packageName, OPSTR_MANAGE_EXTERNAL_STORAGE,
- attributionTag, opMessage);
+ attributionTag, generateAppOpMessage(packageName, sOpDescription.get()));
}
public static boolean checkPermissionWriteStorage(@NonNull Context context, int pid, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
- @Nullable String opMessage) {
+ @NonNull String packageName, @Nullable String attributionTag) {
return noteAppOpPermission(context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE,
- attributionTag, opMessage);
+ attributionTag, generateAppOpMessage(packageName, sOpDescription.get()));
}
public static boolean checkPermissionReadStorage(@NonNull Context context, int pid, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
- @Nullable String opMessage) {
+ @NonNull String packageName, @Nullable String attributionTag) {
return noteAppOpPermission(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE,
- attributionTag, opMessage);
+ attributionTag, generateAppOpMessage(packageName, sOpDescription.get()));
}
public static boolean checkIsLegacyStorageGranted(
@@ -84,73 +90,67 @@
}
public static boolean checkPermissionReadAudio(@NonNull Context context, int pid, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
- @Nullable String opMessage) {
+ @NonNull String packageName, @Nullable String attributionTag) {
if (!checkPermissionAppOp(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE)) {
return false;
}
- return noteAppOpAllowingLegacy(
- context, pid, uid, packageName, OPSTR_READ_MEDIA_AUDIO, attributionTag, opMessage);
+ return noteAppOpAllowingLegacy(context, pid, uid, packageName, OPSTR_READ_MEDIA_AUDIO,
+ attributionTag, generateAppOpMessage(packageName, sOpDescription.get()));
}
public static boolean checkPermissionWriteAudio(@NonNull Context context, int pid, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
- @Nullable String opMessage) {
+ @NonNull String packageName, @Nullable String attributionTag) {
if (!checkPermissionAppOpAllowingNonLegacy(
context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE)) {
return false;
}
- return noteAppOpAllowingLegacy(
- context, pid, uid, packageName, OPSTR_WRITE_MEDIA_AUDIO, attributionTag, opMessage);
+ return noteAppOpAllowingLegacy(context, pid, uid, packageName, OPSTR_WRITE_MEDIA_AUDIO,
+ attributionTag, generateAppOpMessage(packageName, sOpDescription.get()));
}
public static boolean checkPermissionReadVideo(@NonNull Context context, int pid, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
- @Nullable String opMessage) {
+ @NonNull String packageName, @Nullable String attributionTag) {
if (!checkPermissionAppOp(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE)) {
return false;
}
- return noteAppOpAllowingLegacy(
- context, pid, uid, packageName, OPSTR_READ_MEDIA_VIDEO, attributionTag, opMessage);
+ return noteAppOpAllowingLegacy(context, pid, uid, packageName, OPSTR_READ_MEDIA_VIDEO,
+ attributionTag, generateAppOpMessage(packageName, sOpDescription.get()));
}
public static boolean checkPermissionWriteVideo(@NonNull Context context, int pid, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
- @Nullable String opMessage) {
+ @NonNull String packageName, @Nullable String attributionTag) {
if (!checkPermissionAppOpAllowingNonLegacy(
context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE)) {
return false;
}
- return noteAppOpAllowingLegacy(
- context, pid, uid, packageName, OPSTR_WRITE_MEDIA_VIDEO, attributionTag, opMessage);
+ return noteAppOpAllowingLegacy(context, pid, uid, packageName, OPSTR_WRITE_MEDIA_VIDEO,
+ attributionTag, generateAppOpMessage(packageName, sOpDescription.get()));
}
public static boolean checkPermissionReadImages(@NonNull Context context, int pid, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
- @Nullable String opMessage) {
+ @NonNull String packageName, @Nullable String attributionTag) {
if (!checkPermissionAppOp(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE)) {
return false;
}
- return noteAppOpAllowingLegacy(
- context, pid, uid, packageName, OPSTR_READ_MEDIA_IMAGES, attributionTag, opMessage);
+ return noteAppOpAllowingLegacy(context, pid, uid, packageName, OPSTR_READ_MEDIA_IMAGES,
+ attributionTag, generateAppOpMessage(packageName, sOpDescription.get()));
}
public static boolean checkPermissionWriteImages(@NonNull Context context, int pid, int uid,
- @NonNull String packageName, @Nullable String attributionTag,
- @Nullable String opMessage) {
+ @NonNull String packageName, @Nullable String attributionTag) {
if (!checkPermissionAppOpAllowingNonLegacy(
context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE)) {
return false;
}
return noteAppOpAllowingLegacy(context, pid, uid, packageName, OPSTR_WRITE_MEDIA_IMAGES,
- attributionTag, opMessage);
+ attributionTag, generateAppOpMessage(packageName, sOpDescription.get()));
}
/**
* Generates a message to be used with the different {@link AppOpsManager#noteOp} variations.
* If the supplied description is {@code null}, the returned message will be {@code null}.
*/
- public static String generateAppOpMessage(
+ private static String generateAppOpMessage(
@NonNull String packageName, @Nullable String description) {
if (description == null) {
return null;
diff --git a/tests/jni/FuseDaemonTest/AndroidManifest.xml b/tests/jni/FuseDaemonTest/AndroidManifest.xml
index 17e2f22..e475251 100644
--- a/tests/jni/FuseDaemonTest/AndroidManifest.xml
+++ b/tests/jni/FuseDaemonTest/AndroidManifest.xml
@@ -18,6 +18,7 @@
package="com.android.tests.fused" >
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application>
<receiver android:name="com.android.cts.install.lib.LocalIntentSender"
diff --git a/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/src/com/android/tests/fused/FilePathAccessTestHelper.java b/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/src/com/android/tests/fused/FilePathAccessTestHelper.java
index aba6537..e32ee19 100644
--- a/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/src/com/android/tests/fused/FilePathAccessTestHelper.java
+++ b/tests/jni/FuseDaemonTest/FilePathAccessTestHelper/src/com/android/tests/fused/FilePathAccessTestHelper.java
@@ -15,30 +15,28 @@
*/
package com.android.tests.fused;
-import static com.android.tests.fused.lib.ReaddirTestHelper.READDIR_QUERY;
import static com.android.tests.fused.lib.RedactionTestHelper.EXIF_METADATA_QUERY;
import static com.android.tests.fused.lib.RedactionTestHelper.getExifMetadata;
+import static com.android.tests.fused.lib.TestUtils.CAN_READ_WRITE_QUERY;
import static com.android.tests.fused.lib.TestUtils.CREATE_FILE_QUERY;
import static com.android.tests.fused.lib.TestUtils.DELETE_FILE_QUERY;
-import static com.android.tests.fused.lib.TestUtils.CAN_READ_WRITE_QUERY;
import static com.android.tests.fused.lib.TestUtils.INTENT_EXCEPTION;
import static com.android.tests.fused.lib.TestUtils.INTENT_EXTRA_PATH;
import static com.android.tests.fused.lib.TestUtils.OPEN_FILE_FOR_READ_QUERY;
import static com.android.tests.fused.lib.TestUtils.OPEN_FILE_FOR_WRITE_QUERY;
import static com.android.tests.fused.lib.TestUtils.QUERY_TYPE;
+import static com.android.tests.fused.lib.TestUtils.READDIR_QUERY;
import static com.android.tests.fused.lib.TestUtils.canOpen;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
-import android.util.Log;
-
-import com.android.tests.fused.lib.ReaddirTestHelper;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
/**
* App for FilePathAccessTest Functions.
@@ -56,86 +54,91 @@
super.onCreate(savedInstanceState);
String queryType = getIntent().getStringExtra(QUERY_TYPE);
queryType = queryType == null ? "null" : queryType;
- switch (queryType) {
- case READDIR_QUERY:
- sendDirectoryEntries(queryType);
- break;
- case CAN_READ_WRITE_QUERY:
- case CREATE_FILE_QUERY:
- case DELETE_FILE_QUERY:
- case OPEN_FILE_FOR_READ_QUERY:
- case OPEN_FILE_FOR_WRITE_QUERY:
- accessFile(queryType);
- break;
- case EXIF_METADATA_QUERY:
- sendMetadata(queryType);
- break;
- case "null":
- default:
- Log.e(TAG, "Unknown query received from launcher app: " + queryType);
+ Intent returnIntent;
+ try {
+ switch (queryType) {
+ case READDIR_QUERY:
+ returnIntent = sendDirectoryEntries(queryType);
+ break;
+ case CAN_READ_WRITE_QUERY:
+ case CREATE_FILE_QUERY:
+ case DELETE_FILE_QUERY:
+ case OPEN_FILE_FOR_READ_QUERY:
+ case OPEN_FILE_FOR_WRITE_QUERY:
+ returnIntent = accessFile(queryType);
+ break;
+ case EXIF_METADATA_QUERY:
+ returnIntent = sendMetadata(queryType);
+ break;
+ case "null":
+ default:
+ throw new IllegalStateException(
+ "Unknown query received from launcher app: " + queryType);
+ }
+ } catch (Exception e) {
+ returnIntent = new Intent(queryType);
+ returnIntent.putExtra(INTENT_EXCEPTION, e);
}
+ sendBroadcast(returnIntent);
}
- private void sendMetadata(String queryType) {
+ private Intent sendMetadata(String queryType) throws IOException {
final Intent intent = new Intent(queryType);
if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
- try {
- if (EXIF_METADATA_QUERY.equals(queryType)) {
- intent.putExtra(queryType, getExifMetadata(new File(filePath)));
- }
- } catch (Exception e) {
- intent.putExtra(INTENT_EXCEPTION, e);
+ if (EXIF_METADATA_QUERY.equals(queryType)) {
+ intent.putExtra(queryType, getExifMetadata(new File(filePath)));
}
} else {
- Log.e(TAG, "File path not set from launcher app");
- intent.putExtra(INTENT_EXCEPTION, new IllegalStateException(
- "File path not set from launcher app"));
+ throw new IllegalStateException(EXIF_METADATA_QUERY
+ + ": File path not set from launcher app");
}
- sendBroadcast(intent);
+ return intent;
}
- private void sendDirectoryEntries(String queryType) {
+ private Intent sendDirectoryEntries(String queryType) throws IOException {
if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
final String directoryPath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
- ArrayList<String> directoryEntries = new ArrayList<String>();
+ ArrayList<String> directoryEntriesList = new ArrayList<>();
if (queryType.equals(READDIR_QUERY)) {
- directoryEntries = ReaddirTestHelper.readDirectory(directoryPath);
+ final String[] directoryEntries = new File(directoryPath).list();
+ if (directoryEntries == null) {
+ throw new IOException(
+ "I/O exception while listing entries for " + directoryPath);
+ }
+ Collections.addAll(directoryEntriesList, directoryEntries);
}
final Intent intent = new Intent(queryType);
- intent.putStringArrayListExtra(queryType, directoryEntries);
- sendBroadcast(intent);
+ intent.putStringArrayListExtra(queryType, directoryEntriesList);
+ return intent;
} else {
- Log.e(TAG, "Directory path not set from launcher app");
+ throw new IllegalStateException(READDIR_QUERY
+ + ": Directory path not set from launcher app");
}
}
- private void accessFile(String queryType) {
+ private Intent accessFile(String queryType) throws IOException {
if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
final File file = new File(filePath);
boolean returnStatus = false;
- try {
- if (queryType.equals(CAN_READ_WRITE_QUERY)) {
+ if (queryType.equals(CAN_READ_WRITE_QUERY)) {
returnStatus = file.exists() && file.canRead() && file.canWrite();
- } else if (queryType.equals(CREATE_FILE_QUERY)) {
- maybeCreateParentDirInAndroid(file);
- returnStatus = file.createNewFile();
- } else if (queryType.equals(DELETE_FILE_QUERY)) {
- returnStatus = file.delete();
- } else if (queryType.equals(OPEN_FILE_FOR_READ_QUERY)) {
- returnStatus = canOpen(file, false /* forWrite */);
- } else if (queryType.equals(OPEN_FILE_FOR_WRITE_QUERY)) {
- returnStatus = canOpen(file, true /* forWrite */);
- }
- } catch(IOException e) {
- Log.e(TAG, "Failed to access file: " + filePath + ". Query type: " + queryType, e);
+ } else if (queryType.equals(CREATE_FILE_QUERY)) {
+ maybeCreateParentDirInAndroid(file);
+ returnStatus = file.createNewFile();
+ } else if (queryType.equals(DELETE_FILE_QUERY)) {
+ returnStatus = file.delete();
+ } else if (queryType.equals(OPEN_FILE_FOR_READ_QUERY)) {
+ returnStatus = canOpen(file, false /* forWrite */);
+ } else if (queryType.equals(OPEN_FILE_FOR_WRITE_QUERY)) {
+ returnStatus = canOpen(file, true /* forWrite */);
}
final Intent intent = new Intent(queryType);
intent.putExtra(queryType, returnStatus);
- sendBroadcast(intent);
+ return intent;
} else {
- Log.e(TAG, "file path not set from launcher app");
+ throw new IllegalStateException(queryType + ": File path not set from launcher app");
}
}
diff --git a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
index ef05f13..273f200 100644
--- a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
+++ b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
@@ -252,6 +252,11 @@
}
@Test
+ public void testManageExternalStorageReaddir() throws Exception {
+ runDeviceTest("testManageExternalStorageReaddir");
+ }
+
+ @Test
public void testManageExternalStorageCanRenameOtherAppsContents() throws Exception {
runDeviceTest("testManageExternalStorageCanRenameOtherAppsContents");
}
diff --git a/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java b/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java
index 2a5c842..18c240d 100644
--- a/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java
+++ b/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java
@@ -24,6 +24,7 @@
import static com.android.tests.fused.lib.TestUtils.assertCanRenameFile;
import static com.android.tests.fused.lib.TestUtils.assertCanRenameDirectory;
import static com.android.tests.fused.lib.TestUtils.assertCantRenameFile;
+import static com.android.tests.fused.lib.TestUtils.assertDirectoryContains;
import static com.android.tests.fused.lib.TestUtils.assertFileContent;
import static com.android.tests.fused.lib.TestUtils.createFileAs;
import static com.android.tests.fused.lib.TestUtils.deleteFileAsNoThrow;
@@ -61,7 +62,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.cts.install.lib.TestApp;
-import com.android.tests.fused.lib.ReaddirTestHelper;
import com.android.tests.fused.lib.TestUtils;
import com.google.common.io.Files;
@@ -413,8 +413,7 @@
try {
assertThat(videoFile.createNewFile()).isTrue();
- assertThat(ReaddirTestHelper.readDirectory(EXTERNAL_STORAGE_DIR))
- .contains(VIDEO_FILE_NAME);
+ assertDirectoryContains(videoFile.getParentFile(), videoFile);
assertThat(getFileRowIdFromDatabase(videoFile)).isNotEqualTo(-1);
// Legacy app can delete its own file.
diff --git a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/ReaddirTestHelper.java b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/ReaddirTestHelper.java
deleted file mode 100644
index 29986bc..0000000
--- a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/ReaddirTestHelper.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests.fused.lib;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.DirectoryIteratorException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryStream.Filter;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import android.util.Log;
-
-/**
- * Helper functions for readdir tests
- */
-public class ReaddirTestHelper {
- private static final String TAG = "ReaddirTestHelper";
-
- public static final String READDIR_QUERY = "com.android.tests.fused.readdir";
-
- /**
- * Returns directory entries for the given {@code directory}
- *
- * @param directory directory that needs to be listed.
- * @return list of directory names and filenames in the given directory.
- */
- public static ArrayList<String> readDirectory(File directory) {
- return readDirectory(directory.toString());
- }
-
- /**
- * Returns directory entries for the given {@code directoryPath}
- *
- * @param directoryPath path of the directory.
- * @return list of directory names and filenames in the given directory.
- */
- public static ArrayList<String> readDirectory(String directoryPath) {
- Filter<Path> filter = new Filter<Path>() {
- public boolean accept(Path file) {
- return true;
- }
- };
- return readDirectory(directoryPath, filter);
- }
-
- /**
- * Returns filtered directory entries for the given {@code directoryPath}
- *
- * @param directoryPath path of the directory.
- * @param filter filter to apply on directory entries.
- * @return list of directory names and filenames in the given directory. Directory entries are
- * filtered by the given filter.
- */
- public static ArrayList<String> readDirectory(String directoryPath, Filter<Path> filter) {
- ArrayList<String> directoryEntries = new ArrayList<String>();
- File dir = new File(directoryPath);
-
- try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dir.toPath(),
- filter)) {
- for (Path de: directoryStream) {
- directoryEntries.add(de.getFileName().toString());
- }
- } catch (IOException | DirectoryIteratorException x) {
- Log.e(TAG, "IOException occurred while readding directory entries from " +
- directoryPath, x);
- }
- return directoryEntries;
- }
-}
diff --git a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
index 00de011..37a7f67 100644
--- a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
+++ b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
@@ -18,7 +18,6 @@
import static androidx.test.InstrumentationRegistry.getContext;
-import static com.android.tests.fused.lib.ReaddirTestHelper.READDIR_QUERY;
import static com.android.tests.fused.lib.RedactionTestHelper.EXIF_METADATA_QUERY;
import static com.google.common.truth.Truth.assertThat;
@@ -66,7 +65,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -84,6 +85,7 @@
public static final String OPEN_FILE_FOR_READ_QUERY = "com.android.tests.fused.openfile_read";
public static final String OPEN_FILE_FOR_WRITE_QUERY = "com.android.tests.fused.openfile_write";
public static final String CAN_READ_WRITE_QUERY = "com.android.tests.fused.can_read_and_write";
+ public static final String READDIR_QUERY = "com.android.tests.fused.readdir";
public static final String STR_DATA1 = "Just some random text";
public static final String STR_DATA2 = "More arbitrary stuff";
@@ -96,6 +98,8 @@
// Default top-level directories
public static final File ALARMS_DIR = new File(EXTERNAL_STORAGE_DIR,
Environment.DIRECTORY_ALARMS);
+ public static final File ANDROID_DIR = new File(EXTERNAL_STORAGE_DIR,
+ "Android");
public static final File AUDIOBOOKS_DIR = new File(EXTERNAL_STORAGE_DIR,
Environment.DIRECTORY_AUDIOBOOKS);
public static final File DCIM_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_DCIM);
@@ -116,8 +120,12 @@
public static final File RINGTONES_DIR = new File(EXTERNAL_STORAGE_DIR,
Environment.DIRECTORY_RINGTONES);
- public static final File ANDROID_DATA_DIR = new File(EXTERNAL_STORAGE_DIR, "Android/data");
- public static final File ANDROID_MEDIA_DIR = new File(EXTERNAL_STORAGE_DIR, "Android/media");
+ public static final File[] DEFAULT_TOP_LEVEL_DIRS = new File [] { ALARMS_DIR, ANDROID_DIR,
+ AUDIOBOOKS_DIR, DCIM_DIR, DOCUMENTS_DIR, DOWNLOAD_DIR, MUSIC_DIR, MOVIES_DIR,
+ NOTIFICATIONS_DIR, PICTURES_DIR, PODCASTS_DIR, RINGTONES_DIR};
+
+ public static final File ANDROID_DATA_DIR = new File(ANDROID_DIR, "data");
+ public static final File ANDROID_MEDIA_DIR = new File(ANDROID_DIR, "media");
private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
private static final long POLLING_SLEEP_MILLIS = 100;
@@ -130,9 +138,7 @@
* assumptions about their existence.
*/
public static void setupDefaultDirectories() {
- for (File dir : new File [] { ALARMS_DIR, AUDIOBOOKS_DIR, DCIM_DIR,
- DOCUMENTS_DIR, DOWNLOAD_DIR, MUSIC_DIR, MOVIES_DIR, NOTIFICATIONS_DIR,
- PICTURES_DIR, PODCASTS_DIR, RINGTONES_DIR}) {
+ for (File dir : DEFAULT_TOP_LEVEL_DIRS) {
dir.mkdir();
}
}
@@ -209,9 +215,6 @@
String filePath) throws Exception {
HashMap<String, String> res =
getMetadataFromTestApp(testApp, filePath, EXIF_METADATA_QUERY);
- if (res.containsKey(INTENT_EXCEPTION)) {
- throw new IllegalStateException(res.get(INTENT_EXCEPTION));
- }
return res;
}
@@ -452,13 +455,13 @@
return pfd;
}
- public static <T extends Exception> void assertThrows(Class<T> clazz, Operation<T> r)
+ public static <T extends Exception> void assertThrows(Class<T> clazz, Operation<Exception> r)
throws Exception {
assertThrows(clazz, "", r);
}
public static <T extends Exception> void assertThrows(Class<T> clazz, String errMsg,
- Operation<T> r) throws Exception {
+ Operation<Exception> r) throws Exception {
try {
r.run();
fail("Expected " + clazz + " to be thrown");
@@ -677,12 +680,13 @@
String actionName) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final HashMap<String, String> appOutputList = new HashMap<>();
+ final Exception[] exception = new Exception[1];
+ exception[0] = null;
final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.hasExtra(INTENT_EXCEPTION)) {
- appOutputList.put(INTENT_EXCEPTION,
- ((Exception)intent.getExtras().get(INTENT_EXCEPTION)).getMessage());
+ exception[0] = (Exception)(intent.getExtras().get(INTENT_EXCEPTION));
} else if(intent.hasExtra(actionName)) {
HashMap<String, String> res =
(HashMap<String, String>) intent.getExtras().get(actionName);
@@ -692,6 +696,7 @@
}
};
sendIntentToTestApp(testApp, dirPath, actionName, broadcastReceiver, latch);
+ if (exception[0] != null) throw exception[0];
return appOutputList;
}
@@ -702,10 +707,14 @@
String actionName) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final ArrayList<String> appOutputList = new ArrayList<String>();
+ final Exception[] exception = new Exception[1];
+ exception[0] = null;
final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if(intent.hasExtra(actionName)) {
+ if (intent.hasExtra(INTENT_EXCEPTION)) {
+ exception[0] = (Exception)(intent.getSerializableExtra(INTENT_EXCEPTION));
+ } else if(intent.hasExtra(actionName)) {
appOutputList.addAll(intent.getStringArrayListExtra(actionName));
}
latch.countDown();
@@ -713,6 +722,7 @@
};
sendIntentToTestApp(testApp, dirPath, actionName, broadcastReceiver, latch);
+ if (exception[0] != null) throw exception[0];
return appOutputList;
}
@@ -723,10 +733,14 @@
String actionName) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final boolean[] appOutput = new boolean[1];
+ final Exception[] exception = new Exception[1];
+ exception[0] = null;
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if(intent.hasExtra(actionName)) {
+ if (intent.hasExtra(INTENT_EXCEPTION)) {
+ exception[0] = (Exception)(intent.getSerializableExtra(INTENT_EXCEPTION));
+ } else if(intent.hasExtra(actionName)) {
appOutput[0] = intent.getBooleanExtra(actionName, false);
}
latch.countDown();
@@ -734,6 +748,7 @@
};
sendIntentToTestApp(testApp, dirPath, actionName, broadcastReceiver, latch);
+ if (exception[0] != null) throw exception[0];
return appOutput[0];
}
@@ -766,4 +781,15 @@
assertThat(c).isNotNull();
return c;
}
+
+ /**
+ * Asserts that {@code dir} is a directory and that it contains all of {@code expectedContent}
+ */
+ public static void assertDirectoryContains(@NonNull File dir, File... expectedContent) {
+ assertThat(dir.isDirectory()).isTrue();
+ final List<File> actualContent = Arrays.asList(dir.listFiles());
+ for (File f: expectedContent) {
+ assertThat(actualContent).contains(f);
+ }
+ }
}
diff --git a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
index 777c78e..36a4fb6 100644
--- a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
+++ b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
@@ -35,19 +35,33 @@
import static com.android.tests.fused.lib.RedactionTestHelper.assertExifMetadataMismatch;
import static com.android.tests.fused.lib.RedactionTestHelper.getExifMetadata;
import static com.android.tests.fused.lib.RedactionTestHelper.getExifMetadataFromRawResource;
+import static com.android.tests.fused.lib.TestUtils.ALARMS_DIR;
+import static com.android.tests.fused.lib.TestUtils.ANDROID_DATA_DIR;
+import static com.android.tests.fused.lib.TestUtils.ANDROID_MEDIA_DIR;
+import static com.android.tests.fused.lib.TestUtils.AUDIOBOOKS_DIR;
import static com.android.tests.fused.lib.TestUtils.BYTES_DATA1;
import static com.android.tests.fused.lib.TestUtils.BYTES_DATA2;
+import static com.android.tests.fused.lib.TestUtils.DCIM_DIR;
+import static com.android.tests.fused.lib.TestUtils.DEFAULT_TOP_LEVEL_DIRS;
+import static com.android.tests.fused.lib.TestUtils.DOCUMENTS_DIR;
+import static com.android.tests.fused.lib.TestUtils.DOWNLOAD_DIR;
+import static com.android.tests.fused.lib.TestUtils.MOVIES_DIR;
+import static com.android.tests.fused.lib.TestUtils.MUSIC_DIR;
+import static com.android.tests.fused.lib.TestUtils.NOTIFICATIONS_DIR;
+import static com.android.tests.fused.lib.TestUtils.PICTURES_DIR;
+import static com.android.tests.fused.lib.TestUtils.PODCASTS_DIR;
+import static com.android.tests.fused.lib.TestUtils.RINGTONES_DIR;
import static com.android.tests.fused.lib.TestUtils.STR_DATA1;
import static com.android.tests.fused.lib.TestUtils.STR_DATA2;
-import static com.android.tests.fused.lib.TestUtils.assertCanRenameFile;
-import static com.android.tests.fused.lib.TestUtils.assertCanRenameDirectory;
import static com.android.tests.fused.lib.TestUtils.allowAppOpsToUid;
+import static com.android.tests.fused.lib.TestUtils.assertCanRenameDirectory;
+import static com.android.tests.fused.lib.TestUtils.assertCanRenameFile;
import static com.android.tests.fused.lib.TestUtils.assertCantRenameDirectory;
import static com.android.tests.fused.lib.TestUtils.assertCantRenameFile;
+import static com.android.tests.fused.lib.TestUtils.assertDirectoryContains;
import static com.android.tests.fused.lib.TestUtils.assertFileContent;
import static com.android.tests.fused.lib.TestUtils.assertThrows;
import static com.android.tests.fused.lib.TestUtils.canOpen;
-import static com.android.tests.fused.lib.TestUtils.canReadAndWriteAs;
import static com.android.tests.fused.lib.TestUtils.createFileAs;
import static com.android.tests.fused.lib.TestUtils.deleteFileAs;
import static com.android.tests.fused.lib.TestUtils.deleteFileAsNoThrow;
@@ -65,6 +79,7 @@
import static com.android.tests.fused.lib.TestUtils.listAs;
import static com.android.tests.fused.lib.TestUtils.openFileAs;
import static com.android.tests.fused.lib.TestUtils.openWithMediaProvider;
+import static com.android.tests.fused.lib.TestUtils.pollForExternalStorageState;
import static com.android.tests.fused.lib.TestUtils.pollForPermission;
import static com.android.tests.fused.lib.TestUtils.readExifMetadataFromTestApp;
import static com.android.tests.fused.lib.TestUtils.revokePermission;
@@ -72,25 +87,9 @@
import static com.android.tests.fused.lib.TestUtils.uninstallApp;
import static com.android.tests.fused.lib.TestUtils.uninstallAppNoThrow;
import static com.android.tests.fused.lib.TestUtils.updateDisplayNameWithMediaProvider;
-import static com.android.tests.fused.lib.TestUtils.pollForExternalStorageState;
-import static com.android.tests.fused.lib.TestUtils.ALARMS_DIR;
-import static com.android.tests.fused.lib.TestUtils.AUDIOBOOKS_DIR;
-import static com.android.tests.fused.lib.TestUtils.DCIM_DIR;
-import static com.android.tests.fused.lib.TestUtils.DOCUMENTS_DIR;
-import static com.android.tests.fused.lib.TestUtils.DOWNLOAD_DIR;
-import static com.android.tests.fused.lib.TestUtils.MUSIC_DIR;
-import static com.android.tests.fused.lib.TestUtils.MOVIES_DIR;
-import static com.android.tests.fused.lib.TestUtils.NOTIFICATIONS_DIR;
-import static com.android.tests.fused.lib.TestUtils.PICTURES_DIR;
-import static com.android.tests.fused.lib.TestUtils.PODCASTS_DIR;
-import static com.android.tests.fused.lib.TestUtils.RINGTONES_DIR;
-import static com.android.tests.fused.lib.TestUtils.ANDROID_DATA_DIR;
-import static com.android.tests.fused.lib.TestUtils.ANDROID_MEDIA_DIR;
import static com.google.common.truth.Truth.assertThat;
-import static junit.framework.Assert.fail;
-
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
@@ -112,12 +111,10 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.cts.install.lib.TestApp;
-import com.android.tests.fused.lib.ReaddirTestHelper;
import com.google.common.io.Files;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -645,7 +642,6 @@
public void testListFilesFromExternalFilesDirectory() throws Exception {
final String packageName = THIS_PACKAGE_NAME;
final File videoFile = new File(EXTERNAL_FILES_DIR, NONMEDIA_FILE_NAME);
- final String videoFileName = videoFile.getName();
try {
// Create a file in app's external files directory
@@ -654,16 +650,14 @@
}
// App should see its directory and directories of shared packages. App should see all
// files and directories in its external directory.
- assertThat(ReaddirTestHelper.readDirectory(videoFile.getParentFile()))
- .containsExactly(videoFileName);
+ assertDirectoryContains(videoFile.getParentFile(), videoFile);
// Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
// TEST_APP_A should not see other app's external files directory.
installAppWithStoragePermissions(TEST_APP_A);
- // TODO(b/146497700): This is passing because ReaddirTestHelper ignores IOException and
- // returns empty list.
- assertThat(listAs(TEST_APP_A, ANDROID_DATA_DIR.getPath())).doesNotContain(packageName);
- assertThat(listAs(TEST_APP_A, EXTERNAL_FILES_DIR.getPath())).isEmpty();
+
+ assertThrows(IOException.class, () -> listAs(TEST_APP_A, ANDROID_DATA_DIR.getPath()));
+ assertThrows(IOException.class, () -> listAs(TEST_APP_A, EXTERNAL_FILES_DIR.getPath()));
} finally {
videoFile.delete();
uninstallAppNoThrow(TEST_APP_A);
@@ -676,7 +670,6 @@
@Test
public void testListFilesFromExternalMediaDirectory() throws Exception {
final File videoFile = new File(EXTERNAL_MEDIA_DIR, VIDEO_FILE_NAME);
- final String videoFileName = videoFile.getName();
try {
// Create a file in app's external media directory
@@ -686,16 +679,15 @@
// App should see its directory and other app's external media directories with media
// files.
- assertThat(ReaddirTestHelper.readDirectory(EXTERNAL_MEDIA_DIR))
- .containsExactly(videoFileName);
+ assertDirectoryContains(videoFile.getParentFile(), videoFile);
// Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
// TEST_APP_A with storage permission should see other app's external media directory.
installAppWithStoragePermissions(TEST_APP_A);
// Apps with READ_EXTERNAL_STORAGE can list files in other app's external media directory.
assertThat(listAs(TEST_APP_A, ANDROID_MEDIA_DIR.getPath())).contains(THIS_PACKAGE_NAME);
- // TODO(b/145737191) fails because we don't index these files yet.
- assertThat(listAs(TEST_APP_A, EXTERNAL_MEDIA_DIR.getPath())).containsExactly(videoFileName);
+ assertThat(listAs(TEST_APP_A, EXTERNAL_MEDIA_DIR.getPath()))
+ .containsExactly(videoFile.getName());
} finally {
videoFile.delete();
uninstallAppNoThrow(TEST_APP_A);
@@ -1741,6 +1733,36 @@
}
@Test
+ public void testManageExternalStorageReaddir() throws Exception {
+ final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
+ final File otherAppImg = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
+ final File otherAppMusic = new File(MUSIC_DIR, "other" + AUDIO_FILE_NAME);
+ final File otherTopLevelFile = new File(EXTERNAL_STORAGE_DIR, "other" + NONMEDIA_FILE_NAME);
+ try {
+ installApp(TEST_APP_A);
+ assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
+ executeShellCommand("touch " + otherTopLevelFile);
+
+ allowAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
+
+ // We can list other apps' files
+ assertDirectoryContains(otherAppPdf.getParentFile(), otherAppPdf);
+ assertDirectoryContains(otherAppImg.getParentFile(), otherAppImg);
+ assertDirectoryContains(otherAppMusic.getParentFile(), otherAppMusic);
+ // We can list top level files
+ assertDirectoryContains(EXTERNAL_STORAGE_DIR, otherTopLevelFile);
+
+ // We can also list all top level directories
+ assertDirectoryContains(EXTERNAL_STORAGE_DIR, DEFAULT_TOP_LEVEL_DIRS);
+ } finally {
+ denyAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
+ executeShellCommand("rm " + otherTopLevelFile);
+ deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
+ uninstallApp(TEST_APP_A);
+ }
+ }
+
+ @Test
public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
final File otherAppImg = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
diff --git a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
index d581921..5c54e3f 100644
--- a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
@@ -59,17 +59,16 @@
assertTrue(checkPermissionSystem(context, pid, uid, packageName));
assertFalse(checkPermissionBackup(context, pid, uid));
- assertFalse(
- checkPermissionManageExternalStorage(context, pid, uid, packageName, null, null));
+ assertFalse(checkPermissionManageExternalStorage(context, pid, uid, packageName, null));
- assertTrue(checkPermissionReadStorage(context, pid, uid, packageName, null, null));
- assertTrue(checkPermissionWriteStorage(context, pid, uid, packageName, null, null));
+ assertTrue(checkPermissionReadStorage(context, pid, uid, packageName, null));
+ assertTrue(checkPermissionWriteStorage(context, pid, uid, packageName, null));
- assertTrue(checkPermissionReadAudio(context, pid, uid, packageName, null, null));
- assertFalse(checkPermissionWriteAudio(context, pid, uid, packageName, null, null));
- assertTrue(checkPermissionReadVideo(context, pid, uid, packageName, null, null));
- assertFalse(checkPermissionWriteVideo(context, pid, uid, packageName, null, null));
- assertTrue(checkPermissionReadImages(context, pid, uid, packageName, null, null));
- assertFalse(checkPermissionWriteImages(context, pid, uid, packageName, null, null));
+ assertTrue(checkPermissionReadAudio(context, pid, uid, packageName, null));
+ assertFalse(checkPermissionWriteAudio(context, pid, uid, packageName, null));
+ assertTrue(checkPermissionReadVideo(context, pid, uid, packageName, null));
+ assertFalse(checkPermissionWriteVideo(context, pid, uid, packageName, null));
+ assertTrue(checkPermissionReadImages(context, pid, uid, packageName, null));
+ assertFalse(checkPermissionWriteImages(context, pid, uid, packageName, null));
}
}