Merge "Test M_E_S can readdir anywhere on shared external storage" into rvc-dev
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);