5

I am listing all my media files in a recycler view. Suppose a media file is in a folder, then I want to show that folder in my recycler view too. Here is my code to list media files

var projection = arrayOf(MediaStore.Video.Media.DISPLAY_NAME)
var cursor = CursorLoader(applicationContext, MediaStore.Video.Media.EXTERNAL_CONTENT_URI, 
    projection, null, null, null).loadInBackground()

if (cursor != null) {
    while (cursor.moveToNext()) {
        val name = cursor.getString(cursor.getColumnIndex(MediaStore.Video.VideoColumns.DISPLAY_NAME))
        Log.i("Main", name)
    }
    cursor.close()
}

How can I also show the folder if a particular media file is present in a folder. Any help would be greatly appreciated.

Paraskevas Ntsounos
  • 1,755
  • 2
  • 18
  • 34

1 Answers1

1

If you get permission to access any folder using Storage Access Framework, you get URI for that folder you can display that folder, display files inside that folder, edit or delete files without using SAF again unless that folder does not exist. It works both for device memory and external memory like SD card. If you don't have an Uri, imagSaveUriString is Uri in String format, at start set a default folder inside device and get files from that directory using File dir = new File(Environment.getExternalStorageDirectory(), "App Dir");

I have an image gallery that lists images with .png or .jpeg extension inside the folder that was given permission before via SAF

private void setUpGalleryData() {
    listGalleryItems.clear();

    if (FileUtils.isContentUri(imageSaveUriString)) {
        DocumentFile dir = DocumentFile.fromTreeUri(getApplicationContext(), Uri.parse(imageSaveUriString));
        DocumentFile[] documentFiles = dir.listFiles();

        if (dir != null && documentFiles != null && documentFiles.length > 0) {
            for (int i = 0; i < documentFiles.length; i++) {
                if (documentFiles[i].getUri().toString().toLowerCase().endsWith(Config.IMAGE_FORMAT_JPEG)
                        || documentFiles[i].getUri().toString().toLowerCase().endsWith(Config.IMAGE_FORMAT_PNG)) {
                    GalleryItem galleryItem = new GalleryItem();
                    galleryItem.setName(documentFiles[i].getName());
                    galleryItem.setSize(documentFiles[i].length());
                    galleryItem.setPath(documentFiles[i].getUri().toString());
                    galleryItem.setDateLastModified(documentFiles[i].lastModified());

                    listGalleryItems.add(galleryItem);
                }
            }
        }
    } else {

        File dir = new File(Environment.getExternalStorageDirectory(), Config.APP_DIRECTORY);
        File[] files = dir.listFiles();

        if (dir != null && files != null && files.length > 0) {

            for (int i = 0; i < files.length; i++) {
                if (files[i].getAbsolutePath().endsWith(".jpg") || files[i].getAbsolutePath().endsWith(".png")) {

                    GalleryItem galleryItem = new GalleryItem();
                    galleryItem.setName(files[i].getName());
                    galleryItem.setSize(files[i].length());
                    galleryItem.setPath(files[i].getAbsolutePath());
                    galleryItem.setDateLastModified(files[i].lastModified());

                    listGalleryItems.add(galleryItem);
                }
            }
        }
    }
}

And set i send data to adapter with

mAdapter = new GalleryListAdapter(this, listGalleryItems);
mRecyclerView.setAdapter(mAdapter);

FileUtils.isContentUri(imageSaveUriString) checks if URI is a valid content URI checks "com.android.externalstorage.documents".equals(uri.getAuthority())

This is the utility class i use, i got some of it from SO, some written by me, and i think it's a complete util class to get correct URI(file:/// and content) and static file folder from uri. However, you should look for Storage Access Framework. There is a Google sample that display how to create sub folders and find folders inside folders.

  public final class FileUtils {

        private FileUtils() {

        }

        private static final String LOG_TAG = FileUtils.class.getName();

        /**
         * Get absolute paths of memory and SD cards
         * 
         * @param context
         *            Required for getting external starage dirs
         * @return returns external storage paths (directory of external memory card) as
         *         array of Strings
         */
        public static String[] getExternalStorageDirectories(Context context) {

            List<String> results = new ArrayList<>();

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // Method 1 for KitKat & above
                File[] externalDirs = context.getExternalFilesDirs(null);

                for (File file : externalDirs) {
                    String path = file.getPath().split("/Android")[0];

                    boolean addPath = false;

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        addPath = Environment.isExternalStorageRemovable(file);
                    } else {
                        addPath = Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(file));
                    }

                    if (addPath) {
                        results.add(path);
                    }
                }
            }

            if (results.isEmpty()) { // Method 2 for all versions
                // better variation of: http://stackoverflow.com/a/40123073/5002496
                String output = "";
                try {
                    final Process process = new ProcessBuilder().command("mount | grep /dev/block/vold")
                            .redirectErrorStream(true).start();
                    process.waitFor();
                    final InputStream is = process.getInputStream();
                    final byte[] buffer = new byte[1024];
                    while (is.read(buffer) != -1) {
                        output = output + new String(buffer);
                    }
                    is.close();
                } catch (final Exception e) {
                    e.printStackTrace();
                }
                if (!output.trim().isEmpty()) {
                    String devicePoints[] = output.split("\n");
                    for (String voldPoint : devicePoints) {
                        results.add(voldPoint.split(" ")[2]);
                    }
                }
            }

            // Below few lines is to remove paths which may not be external memory card,
            // like OTG (feel free to comment them out)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                for (int i = 0; i < results.size(); i++) {
                    if (!results.get(i).toLowerCase().matches(".*[0-9a-f]{4}[-][0-9a-f]{4}")) {
                        Log.d(LOG_TAG, results.get(i) + " might not be extSDcard");
                        results.remove(i--);
                    }
                }
            } else {
                for (int i = 0; i < results.size(); i++) {
                    if (!results.get(i).toLowerCase().contains("ext") && !results.get(i).toLowerCase().contains("sdcard")) {
                        Log.d(LOG_TAG, results.get(i) + " might not be extSDcard");
                        results.remove(i--);
                    }
                }
            }

            String[] storageDirectories = new String[results.size()];
            for (int i = 0; i < results.size(); ++i)
                storageDirectories[i] = results.get(i);

            return storageDirectories;
        }

        /**
         * Gets File from DocumentFile if Uri is File Uri starting with file:///
         *
         * @param documentFile
         *            Document file that contains Uri to create File from
         * @return File with absolute path to the physical file on device's memory
         */
        public static File getFileFromFileUri(DocumentFile documentFile) {
            try {
                File file = new File(URI.create(documentFile.getUri().toString()));
                return file;
            } catch (Exception e) {
                return null;
            }
        }

        /**
         * Returns File with absolute path to physical file in memory. Uri should be a
         * valid File Uri starting with file:///
         * 
         * @param uriString
         *            Should contain a valid File Uri path
         * @return File pointing to physical file in memory
         */
        public static File getFileFromFileUri(String uriString) {
            try {
                Uri uri = Uri.parse(uriString);
                File file = new File(URI.create(uri.toString()));
                return file;
            } catch (Exception e) {
                return null;
            }
        }

        /**
         * Gets absolute path of a file in SD Card if Uri of Document file is content
         * Uri content:// .
         *
         * @param documentFile
         *            DocumentFile Uri is content uri
         * @return Absolute path of the file
         */

        public static String getSDCardPath(DocumentFile documentFile) {
            // We can't get absolute path from DocumentFile or Uri.
            // It is a hack to build absolute path by DocumentFile.
            // May not work on some devices.
            try {
                Uri uri = documentFile.getUri();
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");

                String sd = null;
                sd = System.getenv("SECONDARY_STORAGE");


                if (sd == null) {
                    // sd = System.getenv("EXTERNAL_STORAGE");

                    String documentPath = "/storage" + "/" + docId.replace(":", "/");
                    return documentPath;
                }
                if (sd != null) {
                    // On some devices SECONDARY_STORAGE has several paths
                    // separated with a colon (":"). This is why we split
                    // the String.
                    String[] paths = sd.split(":");
                    for (String p : paths) {
                        File fileSD = new File(p);
                        if (fileSD.isDirectory()) {
                            sd = fileSD.getAbsolutePath();
                        }
                    }
                    String id = split[1];
                    String documentPath = sd + "/" + id;
                    return documentPath;
                }
            } catch (Exception e) {
                System.out.println("FileUtils ERROR " + e.toString());
                return null;
            }

            return null;
        }

        /**
         * Get a file path from a Uri. This will get the the path for Storage Access
         * Framework Documents, as well as the _data field for the MediaStore and other
         * file-based ContentProviders.
         *
         * @param context
         *            The context.
         * @param uri
         *            The Uri to query.
         */
        public static String getPath(final Context context, final Uri uri) {

            final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

            try {
                // DocumentProvider
                if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {

                    // ExternalStorageProvider
                    if (isContentUri(uri)) {
                        final String docId = DocumentsContract.getDocumentId(uri);
                        final String[] split = docId.split(":");
                        final String type = split[0];

                        if ("primary".equalsIgnoreCase(type)) {
                            if (split.length > 1) {
                                return Environment.getExternalStorageDirectory() + "/" + split[1] + "/";
                            } else {
                                return Environment.getExternalStorageDirectory() + "/";
                            }
                        } else {
                            return "storage" + "/" + docId.replace(":", "/");
                        }

                    }
                    // DownloadsProvider
                    else if (isDownloadsDocument(uri)) {

                        final String id = DocumentsContract.getDocumentId(uri);
                        final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),
                                Long.valueOf(id));

                        return getDataColumn(context, contentUri, null, null);
                    }
                    // MediaProvider
                    else if (isMediaDocument(uri)) {
                        final String docId = DocumentsContract.getDocumentId(uri);
                        final String[] split = docId.split(":");
                        final String type = split[0];

                        Uri contentUri = null;
                        if ("image".equals(type)) {
                            contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                        } else if ("video".equals(type)) {
                            contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                        } else if ("audio".equals(type)) {
                            contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                        }

                        final String selection = "_id=?";
                        final String[] selectionArgs = new String[] { split[1] };

                        return getDataColumn(context, contentUri, selection, selectionArgs);
                    }
                }
                // MediaStore (and general)
                else if ("content".equalsIgnoreCase(uri.getScheme())) {

                    // Return the remote address
                    if (isGooglePhotosUri(uri))
                        return uri.getLastPathSegment();

                    return getDataColumn(context, uri, null, null);
                }
                // File
                else if ("file".equalsIgnoreCase(uri.getScheme())) {
                    return uri.getPath();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        /**
         * Get the value of the data column for this Uri. This is useful for MediaStore
         * Uris, and other file-based ContentProviders.
         *
         * @param context
         *            The context.
         * @param uri
         *            The Uri to query.
         * @param selection
         *            (Optional) Filter used in the query.
         * @param selectionArgs
         *            (Optional) Selection arguments used in the query.
         * @return The value of the _data column, which is typically a file path.
         */
        public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {

            Cursor cursor = null;
            final String column = "_data";
            final String[] projection = { column };

            try {
                cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
                if (cursor != null && cursor.moveToFirst()) {
                    final int index = cursor.getColumnIndexOrThrow(column);
                    return cursor.getString(index);
                }
            } finally {
                if (cursor != null)
                    cursor.close();
            }
            return null;
        }

        /**
         * Checks if a string is parsable to Content Uri
         * 
         * @param uriString
         *            checked if can be parsed to a Content Uri
         * @return uriString is a content uri
         */
        public static boolean isContentUri(String uriString) {
            Uri uri = null;
            try {
                uri = Uri.parse(uriString);
            } catch (NullPointerException e) {
                return false;
            }
            return isContentUri(uri);
        }

        /**
         * @param uri
         *            The Uri to check.
         * @return Whether the Uri authority is ExternalStorageProvider.
         */
        public static boolean isContentUri(Uri uri) {
            if (uri == null) {
                return false;
            } else {
                return "com.android.externalstorage.documents".equals(uri.getAuthority());
            }
        }

        /**
         * @param uri
         *            The Uri to check.
         * @return Whether the Uri authority is DownloadsProvider.
         */
        public static boolean isDownloadsDocument(Uri uri) {
            return "com.android.providers.downloads.documents".equals(uri.getAuthority());
        }

        /**
         * @param uri
         *            The Uri to check.
         * @return Whether the Uri authority is MediaProvider.
         */
        public static boolean isMediaDocument(Uri uri) {
            return "com.android.providers.media.documents".equals(uri.getAuthority());
        }

        /**
         * @param uri
         *            The Uri to check.
         * @return Whether the Uri authority is Google Photos.
         */
        public static boolean isGooglePhotosUri(Uri uri) {
            return "com.google.android.apps.photos.content".equals(uri.getAuthority());
        }

    }
Thracian
  • 43,021
  • 16
  • 133
  • 222
  • I don't understand what is FileUtils.isContentUri(imageSaveUriString). –  May 31 '18 at 18:26
  • Config.APP_DIRECTORY gives error. Even if I import the package Config does not have anything like APP_DIRECTORY. android.util package is deprecated –  May 31 '18 at 18:29
  • @Thracian Not a right answer. Stop posting any answer just for free bounties –  May 31 '18 at 18:32
  • @Nachhhh FileUtils.isContentUri(imageSaveUriString) is for checking if it's a content uri. it returns boolean from "com.android.externalstorage.documents".equals(uri.getAuthority()) this. – Thracian Jun 01 '18 at 06:08
  • @Nachhhh Config is a final class i created to keep Strings. It contains directory name, ".png", ".jpg" as String. – Thracian Jun 01 '18 at 06:09
  • @Nachhhh, there is also no pre-defined GallerItem object either. It's a class for data with only fields, setters and getters. – Thracian Jun 01 '18 at 06:13
  • @Thracian can you post your Config and FileUtils class –  Jun 01 '18 at 08:14
  • @Nachhhh I added whole class of FileUtils. Config only keeps strings, it's final class for storing constants. I edited that too. For example, `Config.APP_DIRECTORY = "My App Folder";` and `Config.IMAGE_FORMAT_JPEG = ".jpg";` These are not for your app, you should customize your app for your file extensions and folders. I only added valid solution for getting file folder and editing files inside the folder. I removed unnecessary parts. – Thracian Jun 01 '18 at 09:01