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));
     }
 }