16

According to the docs file path access is granted in Android R:

Starting in Android 11, apps that have the READ_EXTERNAL_STORAGE permission can read a device's media files using direct file paths and native libraries. This new capability allows your app to work more smoothly with third-party media libraries.

The problem is that I can't get the file path from MediaStore, so how are we supposed to read a file path that we can't access/retrieve? Is there a way, I'm not aware of, that we can get the file path from MediaStore?


Furthermore, the docs say the following:

All Files Access

Some apps have a core use case that requires broad file access, such as file management or backup & restore operations. They can get All Files Access by doing the following:

  1. Declare the MANAGE_EXTERNAL_STORAGE permission.
  2. Direct users to a system settings page where they can enable the Allow access to manage all files option for your app.

This permission grants the following:

  • Read access and write access to all files within shared storage.
  • Access to the contents of the MediaStore.Files table.

But I do not need all file access, I only want the user to select a video from MediaStore and pass the file path to FFmpeg(it requires a file path). I know that I can no longer use the _data column to retrieve a file path.


Please note:

  • I know a Uri is returned from MediaStore and does not point to a file.
  • I know that I can copy the file to my application directory and pass that to FFmpeg, but I could do that before Android R.
  • I can not pass FileDescriptor to FFmpeg and I can not use /proc/self/fd/ (I get /proc/7828/fd/70: Permission denied when selecting a file from the SD Card), have a look at this issue.

So what am I supposed to do, am I missing something? What was meant with can read a device's media files using direct file paths and native libraries?

HB.
  • 3,514
  • 3
  • 18
  • 41

2 Answers2

13

After asking a question on issuetracker, I've come to the following conclusions:

  • On Android R, the File restrictions that were added in Android Q is removed. So we can once again access File objects.
  • If you are targeting Android 10 > and you want to access/use file paths, you will have to add/keep the following in your manifest:

    android:requestLegacyExternalStorage="true"
    

    This is to ensure that file paths are working on Android 10(Q). On Android R this attribute will be ignored.

  • Don't use DATA column for inserting or updating into Media Store, use DISPLAY_NAME and RELATIVE_PATH, here is an example:

    ContentValues valuesvideos;
    valuesvideos = new ContentValues();
    valuesvideos.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/" + "YourFolder");
    valuesvideos.put(MediaStore.Video.Media.TITLE, "SomeName");
    valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, "SomeName");
    valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
    valuesvideos.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
    valuesvideos.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
    valuesvideos.put(MediaStore.Video.Media.IS_PENDING, 1);
    ContentResolver resolver = getContentResolver();
    Uri collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
    Uri uriSavedVideo = resolver.insert(collection, valuesvideos);
    
  • You can no longer use the ACTION_OPEN_DOCUMENT_TREE or the ACTION_OPEN_DOCUMENT intent action to request that the user select individual files from Android/data/,Android/obb/and all sub-directories.

  • It is recommended to only use File objects when you need to perform "seeking", like when using FFmpeg, for example.
  • You can only use the data column to access files that are on the disk. You should handle I/O Exceptions accordingly.

If you want to access a File or want a file path from a Uri that was returned from MediaStore, I've created a library that handles all the exceptions you might get. This includes all files on the disk, internal and removable disk. When selecting a File from Dropbox, for example, the File will be copied to your applications directory where you have full access, the copied file path will then be returned.

HB.
  • 3,514
  • 3
  • 18
  • 41
  • Do you also have, perhaps, a sample on Github for the auto-finding of the media files , to list them? – android developer Jun 29 '20 at 22:33
  • @androiddeveloper "auto-finding of the media files" I'm not sure I understand what you are asking? – HB. Jun 30 '20 at 10:31
  • Aren't gallery apps and music apps finding their files using an API from the frameworks automatically, and that a special file (".nomedia") in each folder tells the framework to avoid scanning it? – android developer Jul 01 '20 at 07:28
  • @HB I use your library and test it for Android 11. pickiT.getPath return null. Issue has place when path resolved as media and getDataColumn() return null. Is cursor work for Android 11? May be I not understand anything? – Anton Stukov Nov 20 '20 at 06:25
  • @AntonStukov Look at the "read me", it explains how to use the library (you shouldn't use `pickiT.getPath` directly, the path will be returned in `PickiTonCompleteListener`). If you still can't get it working, open an issue on the library and fill in the issue template (include you log and how you implemented the library) - https://github.com/HBiSoft/PickiT#implementation – HB. Nov 20 '20 at 06:50
  • Could you implement something similar for Ionic? – Shinichi Kudo Feb 03 '21 at 15:51
0

For getting path, i'm coping file with fileDescriptor to new path & i use that path.

Finding File Name:

private static String copyFileAndGetPath(Context context, Uri realUri, String id) {
    final String selection = "_id=?";
    final String[] selectionArgs = new String[]{id};
    String path = null;
    Cursor cursor = null;
    try {
        final String[] projection = {"_data", "_display_name"};
        cursor = context.getContentResolver().query(realUri, projection, selection, selectionArgs,
                null);
        cursor.moveToFirst();
        final String fileName = cursor.getString(cursor.getColumnIndexOrThrow("_display_name"));
        File file = new File(context.getCacheDir(), fileName);

        FileUtils.saveAnswerFileFromUri(realUri, file, context);
        path = file.getAbsolutePath();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return path;
}

Copy With File Descriptor:

fun saveAnswerFileFromUri(uri: Uri, destFile: File?, context: Context) {
    try {
        val pfd: ParcelFileDescriptor =
            context.contentResolver.openFileDescriptor(uri, "r")!!
        if (pfd != null) {
            val fd: FileDescriptor = pfd.getFileDescriptor()
            val fileInputStream: InputStream = FileInputStream(fd)
            val fileOutputStream: OutputStream = FileOutputStream(destFile)
            val buffer = ByteArray(1024)
            var length: Int
            while (fileInputStream.read(buffer).also { length = it } > 0) {
                fileOutputStream.write(buffer, 0, length)
            }

            fileOutputStream.flush()
            fileInputStream.close()
            fileOutputStream.close()
            pfd.close()
        }
    } catch (e: IOException) {
        Timber.w(e)
    }

}
Hosein Haqiqian
  • 218
  • 3
  • 11