18

I'm successfully implementing a method for retrieving the real path of an image from gallery by the Uri returned from ACTION_PICK intent. Here's a sample:

// getRealPathFromURI(intent.getData());

private String getRealPathFromURI(Uri contentURI) {
    String result;
    Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
    if (cursor == null) { // Source is Dropbox or other similar local file path
        result = contentURI.getPath();
    } else { 
        cursor.moveToFirst(); 
        int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); 
        result = cursor.getString(idx);
        cursor.close();
    }
    return result;
}

Just like this answer.

Recently updated the compileSdkVersion to 29 and apparently the DATA attribute everyone's using is deprecated. In the official docs, they recommend to use FileDescriptor instead, problem is i don't know exactly how.
Only thing i found is this question. Didn't find a proper answer there though.

Please help me overcome that deprecation issue with a solution using the suggested way or any other way.

Thank you.


Update:

Followed @CommonsWare's answer and copied the returned Uri (of an image the user picked) to a local directory, using context.getContentResolver.openInputStream(Uri).

Even tried retrieving a file from Google Drive - and it worked. Only problem was the long time it took (about 20 sec for 5MB file).

As a bonus, i was cleared to remove external storage permissions, which one doesn't need for using app's local directories.

No more externals paths for me!

ronginat
  • 1,910
  • 1
  • 12
  • 23
  • 1
    Try to just copy the data having an uri object. As @CommonsWare said `FileOutputStream(yourFullPath).use { oStream -> contentResolver.openInputStream(uri)?.use { iStream -> oStream.write(iStream.readBytes()) } }` – Stanley Wintergreen Jul 18 '19 at 12:05

2 Answers2

21

This question came up for me too a week ago.

My solution was to create an InputStream from the URI and then, from this, create an OutputStream by copying the contents of the input stream.

Note: You could call this method using an asynchronous call because copying extremely large files could have some delays and you won't want to block your UI

@Nullable
public static String createCopyAndReturnRealPath(
       @NonNull Context context, @NonNull Uri uri) {
    final ContentResolver contentResolver = context.getContentResolver();
    if (contentResolver == null)
        return null;

    // Create file path inside app's data dir
    String filePath = context.getApplicationInfo().dataDir + File.separator
            + System.currentTimeMillis();

    File file = new File(filePath);
    try {
        InputStream inputStream = contentResolver.openInputStream(uri);
        if (inputStream == null)
            return null;

        OutputStream outputStream = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int len;
        while ((len = inputStream.read(buf)) > 0)
            outputStream.write(buf, 0, len);

        outputStream.close();
        inputStream.close();
    } catch (IOException ignore) {
        return null;
    }

    return file.getAbsolutePath();
}
dafNou
  • 339
  • 4
  • 10
11

I'm successfully implementing a method for retrieving the real path of an image from gallery by the Uri returned from ACTION_PICK intent.

That code may not work for all images. There is no requirement for DATA to point to a filesystem path that you can access.

Just like this answer.

FWIW, this was my answer to that question.

Only thing i found is this question. Didn't find a proper answer there though.

That technique wasn't particularly good and will no longer work, as Android has locked down /proc.

In the official docs, they recommend to use FileDescriptor instead, problem is i don't know exactly how.

The more general concept is that you use ContentResolver to work with the Uri, whether you get an InputStream (openInputStream()), OutputStream (openOutputStream()), or FileDescriptor. Consume the content using those things. If you have some API that absolutely needs a file, copy the content (e.g., from the InputStream) to a file that you control (e.g., in getCacheDir()).

As a bonus, now your code is also in position to use the Storage Access Framework (e.g., ACTION_OPEN_DOCUMENT) and the Internet (e.g., OkHttp), if and when that would be useful.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • For my app i need the path to play an audio file which i got from MediaStore.Audio.Media.DATA but is the column MediaStore.Audio.Media._ID not enough to get the uri? Then i use ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id) so it returns the content uri which i then can use in mediaPlayer.setDataSource? But for some reason it shows cannot resolve method setDataSource(android.net.Uri) – Vince VD Mar 31 '20 at 19:30
  • 1
    @VinceVD: It is `setDataSource(Context, Uri)`: https://developer.android.com/reference/android/media/MediaPlayer#setDataSource(android.content.Context,%20android.net.Uri) – CommonsWare Mar 31 '20 at 19:33
  • I have another question about this, i just found on your blog "that the MediaStore Uri might be indexing media on removable storage, and has no direct filesystem access to that". So does that mean when i'm playing songs from my sd card wouldn't work? I just tried it with media files on my sd card and it works fine using the content uris i get with song.setData(ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cursor.getLong(cursor.getColumnIndexOrThrow(SONG_ID)))); (source:https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html) – Vince VD Apr 07 '20 at 01:51
  • 1
    @VinceVD: "i just found on your blog" -- please use copy-and-paste for quotes, as you did not get this one correct. It is "Plus even if you get a MediaStore Uri, that might be indexing media on removable storage, and you have no direct filesystem access to that.". "So does that mean when i'm playing songs from my sd card wouldn't work?" -- the `MediaStore` has direct filesystem access to the media. Your app does not. If you are using a `MediaStore` `Uri`, then the `MediaStore` is the code accessing the filesystem, not your app. – CommonsWare Apr 07 '20 at 10:46
  • Ok so if the MediaStore Uri has direct filesystem access, does that mean i can just delete the content with `contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaStore.Audio.Media._ID + "=?", {Long.toString(songId)});` ? – Vince VD Apr 07 '20 at 13:30
  • @VinceVD: Unfortunately, no. That just deletes the entry from the `MediaStore` tables. I do not know of a way, using `MediaStore`, to delete content. – CommonsWare Apr 07 '20 at 13:30
  • So does that mean it's just not possible to delete this file with just the content uri? Before Api 29 i relied on the DATA column to get the path which is now deprecated but i also found alot of comments that it was just bad to rely on this column returning a path, i then just used file.delete for internal files and documentFile.delete() (SAF) for external which both worked but how can i achieve the same thing without using the DATA column from MediaStore? – Vince VD Apr 07 '20 at 17:26
  • @VinceVD: "So does that mean it's just not possible to delete this file with just the content uri?" -- as I wrote, I have no idea how you would do it. – CommonsWare Apr 07 '20 at 18:05