212

Before the new gallery access in Android 4.4 (KitKat) I got my real path on the SD card with this method:

public String getPath(Uri uri) {
   String[] projection = { MediaStore.Images.Media.DATA };
   Cursor cursor = managedQuery(uri, projection, null, null, null);
   startManagingCursor(cursor);
   int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
   cursor.moveToFirst();
 return cursor.getString(column_index);
}

Now, the Intent.ACTION_GET_CONTENT return different data:

Before:

content://media/external/images/media/62

Now:

content://com.android.providers.media.documents/document/image:62

How could I manage to obtain the real path on the SD card?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Álvaro
  • 2,872
  • 4
  • 20
  • 25
  • 2
    I think you can find the solution here http://stackoverflow.com/questions/19834842/android-gallery-on-kitkat-returns-different-uri-for-intent-action-get-content – Ahmed Dini Nov 20 '13 at 06:09
  • 1
    Do you really need a path? Maybe you just want to get a Bitmap object from URI? If so I have a solution for you. – kamil zych Nov 25 '13 at 09:29
  • 6
    Yes, I really need a path and I would like to obtain it from new storage access framework. My provisional solution has been avoiding the new storage acces. I have changed my Intent.ACTION_GET_CONTENT for Intent.ACTION_PICK – Álvaro Nov 27 '13 at 09:23
  • @Alvaro, what did you end up doing – Snake Dec 19 '13 at 20:28
  • Not able to select an item in google drive from my app in the version of 4.4.2. i am completely stuck ? any solution – Madhu May 07 '15 at 10:46
  • This one is totally different from the one that is referred as duplication. So this question should not be specified as duplicated. – Shangwu Mar 29 '17 at 20:58

9 Answers9

530

This will get the file path from the MediaProvider, DownloadsProvider, and ExternalStorageProvider, while falling back to the unofficial ContentProvider method you mention.

/**
 * 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.
 * @author paulburke
 */
public static String getPath(final Context context, final Uri uri) {

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

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }

            // TODO handle non-primary volumes
        }
        // 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 getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    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 column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);
        }
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}


/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
public static boolean isExternalStorageDocument(Uri uri) {
    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());
}

These are taken from my open source library, aFileChooser.

Paul Burke
  • 25,496
  • 9
  • 66
  • 62
  • 2
    Just a note: for provider that requires network, the code returns null. Tested with Google Drive and Picasa. My solution is copying the file to local cache dir. – X.Y. Feb 14 '14 at 01:06
  • 37
    Be sure to check the latest version of these methods for changes at https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java – Paul Burke Feb 14 '14 at 12:16
  • 19
    I can't seem to get this to work with files from Google Drive com.google.android.apps.docs.files – YoungDinosaur Apr 03 '14 at 20:22
  • 8
    @PaulBurke - Thank you Man, but what is that "TODO handle non-primary volumes"? –  Apr 18 '14 at 11:11
  • I had a problem with devices less than API 19. DocumentsContract is added in API 19, but DocumentsContract.isDocumentUri is executed even if API is less than 19. The solution was to move isKitKat into a sepate higher if: if (isKitKat) { if (DocumentsContract.isDocumentUri(context, uri)) { ... – Andy Jun 05 '14 at 05:10
  • isn't there a way to get the path if the file is choosen from google drive? – jcesarmobile Jun 11 '14 at 10:14
  • can it check if the file from the cloud? – LK Yeung Jul 26 '14 at 08:04
  • Sadly this doesn't work if the file is on an SD card (the TODO part of the code). Creating a temp file (yuck!) worked for me: http://stackoverflow.com/a/21443820/973379 – Piotr Zawadzki Aug 18 '14 at 09:20
  • Works great just want to add that while returning path excludes "file://" prefixed, so people can prefix it if they are transfering URL to other applications which require URI intent. For example: I transferred the image URL to crop via intent for default gallery and it used to break. So, just put some ternary check (!urlString.contains("file://")?"file://"+urlString:urlString) and this will fix the issue. – abhy Sep 13 '14 at 12:57
  • 3
    A `Uri` is not necessarily a file: http://commonsware.com/blog/2014/07/04/uri-not-necessarily-file.html – CommonsWare Oct 08 '14 at 16:30
  • 5
    @PaulBurke, it would be nice if you answered some of the questions raised here almost 1 year ago... I want to use your lib, but with so many unanswered issues and with the last commit 1 year ago my conclusion is that it was discontinued. – pertz Dec 05 '14 at 23:52
  • I have checked it in various devices from 4.1 to 5.0.1. It works perfectly in all of them. Thanks a ton. – Aritra Roy Jan 28 '15 at 16:24
  • My app crashes with the follwing exception, when I capture image from camera and trying to get the image path using the getPath(); Error : Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List android.net.Uri.getPathSegments()' on a null object reference 01-31 17:02:10.947: E/AndroidRuntime(20720): at android.provider.DocumentsContract.isDocumentUri(DocumentsContract.java:698). – Akhil Feb 03 '15 at 07:07
  • This worked great for me, thanks! I've packaged it into a Cordova plugin for anyone else who'd like to use it - https://github.com/hiddentao/cordova-plugin-filepath. I had to remove one small bit specific to aFileChooser but everything else is the same. – hiddentao Apr 01 '15 at 05:23
  • 2
    returns null when content://com.google.android.apps.photos.contentprovider – User Jun 02 '15 at 03:02
  • Please put `@TargetApi(19)` before the function declaration for the `DocumentsContract.isDocumentUri()` call. – Ethan Allen Jul 09 '15 at 21:38
  • 24
    This will be useless on hundreds of millions of Android devices (anything that shipped with Android 4.4+) where the `Uri` points to something on removable storage, as you do not have arbitrary access to removable storage. It will also fail for any `Uri` that is from some other `ContentProvider` that does not happen to support the `_data` pattern (e.g., `FileProvider`). A `Uri` is an opaque handle; treat it as such, using `ContentResolver` to get at the content identified by the `Uri`. – CommonsWare Jul 29 '15 at 17:27
  • Hi @CommonsWare, when you say use the Uri as an opaque handle what exactly do you mean? I am aware of ContentResolver.openInputStream(uri), however, this only allows me to read the stream of data. In my particular case I need to copy a file to users Dropbox folder (via Dropbox SDK which expects a File: https://www.dropbox.com/developers/core/start/android#uploading). I can't infer the type of the file from the input stream though so even if I use the input stream to create a temporary file I don't know what type of file (extension) I should be using. Unless I"m missing something? – b.lyte Aug 07 '15 at 22:17
  • 1
    @clu: "when you say use the Uri as an opaque handle what exactly do you mean?" -- I am saying that the characters in the `Uri` itself are meaningless. "I can't infer the type of the file from the input stream " -- which is why [`ContentResover` has `getType()`](http://developer.android.com/reference/android/content/ContentResolver.html#getType%28android.net.Uri%29). "I don't know what type of file (extension) I should be using" -- there is no requirement for a `Uri` to have a file extension, and many will not. – CommonsWare Aug 07 '15 at 22:20
  • @Akhil because if use camera to get image then you wont be getting any Uri in `data.getData()`, but you will have direct access to the Bitmap itself in your onAcitivtyResult, `picture = (Bitmap) data.getExtras().get("data");` – Bhargav Sep 02 '15 at 08:05
  • 2
    This doesn't work for me on LG Nexus 5, with Android 4.4.4, I get FileNotFoundException when opening the file path return. The URI is like this: content://com.android.providers.downloads.documents/document/532 and the file path returned from this method is /storage/emulated/0/Download/portrait.jpg In this scenario, in the UI, there are two images in the Downloads picker called portrait.jpg, so it seems odd that the filename is so simple, eg which portrait.jpg is it referring to. – waterlooalex Oct 13 '15 at 23:23
  • For a few of my users I am getting the path as null if the uri.getScheme() is content using this code. Any idea how to fix this @PaulBurke as it is a major bug in app? – Code Nov 04 '15 at 16:14
  • Maybe it's ok for those cases where you only need to show this image, because you get an emulated uri. But it doesn't work when you need to get the original file and modify it – Fernando Prieto Moyano Dec 14 '15 at 14:47
  • Trying to get a path from a content URI always throws and exception "java.lang.IllegalArgumentException: column '_data' does not exist". Any ideas? – lostintranslation Dec 18 '15 at 18:56
  • 1
    This method won't work if file is from `FileProvider` – alijandro Jan 12 '16 at 03:31
  • For those whose minimum sdk is set to 19...i have modified paul's answer which works fine for me on my nexus 5. follow the link http://stackoverflow.com/a/34903176/4685534 – parvez rafi Jan 21 '16 at 06:17
  • This is a api level 19+ solution... – Loenix Jan 28 '16 at 15:27
  • Has anyone able to address two concerns 1) what to do with non-primary shares and 2) concerns raised by @CommonsWare about this not working with `ContentProvider that does not happen to support the _data pattern`? – Sudhir Singh Khanger Sep 10 '16 at 01:47
  • I am getting crash in Marshmellow following this. Getting `permission denied reading com.android.providers.media.mediaprovider uri content` . I have asked run-time `READ_EXTERNAL_STORAGE`. But still not working. – Nitesh Verma Nov 05 '16 at 12:19
  • 1
    I get `Caused by: java.lang.NumberFormatException: For input string: "383:backup-raw.zip"`. Do you need to perform `split` in case `if (isDownloadsDocument(uri)) {` block? – Cheok Yan Cheng Nov 12 '16 at 15:24
  • Hi..Thanks for the answer... But it is not working in my case.We are getting path something like - content://com.google.android.apps.docs.storage/..... Can any one help us with this? – Aditi Parikh Feb 25 '17 at 08:41
  • 1
    @PaulBurke getPath() will return null for external sdcard files.Any solution for that?? – KJEjava48 May 02 '17 at 13:40
  • as @CheokYanCheng mentioned i also get NumberFormatException at contentUri=ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));... any solution ? – Tejas Pandya Sep 27 '17 at 06:45
  • @Tej I'm seeking for a solution for this issue as well. We are not alone. Here is a hotfix consuming this error in some library: https://github.com/severianremi/uCrop/commit/9e2bb067631ac3bd3817c0fb55c51db3d61edfe0 – Bringoff Sep 29 '17 at 08:17
  • @Tej oh, I've just fixed it for me. Part for downloadProvider if should be fixed with adding following: `final String id = DocumentsContract.getDocumentId(uri);` `if (!TextUtils.isEmpty(id)) {` `if (id.startsWith("raw:")) {` `return id.replaceFirst("raw:", "");` `}` `...` – Bringoff Sep 29 '17 at 08:28
  • Thank you for solution but on some devices I have java.lang.IllegalArgumentException: column '_data' does not exist in getDataColumnMethod. Do you have any Ideas how to fix it? – Sergey Pekar Nov 08 '17 at 13:03
  • For anyone in the future: Some time ago I wrote a version of this for Xamarin.Android and have it here: https://gist.github.com/steveevers/a5af24c226f44bb8fdc3. It's been running in production, without issue, for a few years now. – Steven Evers Nov 15 '17 at 22:56
  • 4
    it's not working for API level 27, oreo 8.1 where I am getting illegal argument exception when picking a image/doc file. – Ajay Chauhan Oct 03 '18 at 09:00
  • 4
    It's not working for Oreo devices, if the content uri is `content://com.android.providers.downloads.documents/document/4016`. I tried with the above code mentioned but got crash like: `Caused by: java.lang.IllegalArgumentException: Unknown URI: content://downloads/public_downloads/4016` – Rajeev Sahu Jan 14 '19 at 13:15
  • When I choose video from folder DOWNLOAd. App Crash – Nguyễn Trung Hiếu Aug 29 '19 at 06:07
  • Method threw 'java.lang.IllegalArgumentException' exception, for the following uri "content://com.lenovo.FileBrowser.FileProvider/root_path/storage/emulated/0/WhatsApp/Media/WhatsApp%20Documents/YagYan.pdf" for OREO 8.1 – ice spirit Aug 29 '19 at 15:32
  • @CommonsWare Can you give correct way to pick any file from Intent and then convert it to a File then, which will work on oreo and above? The Problem i am facing is while picking files from recent items i am not getting file extension same problem as Mr. clu and while picking file from a explorer i am getting illegalArgumentException. – ice spirit Aug 30 '19 at 06:51
  • @icespirit: "Can you give correct way to pick any file from Intent and then convert it to a File then, which will work on oreo and above?" -- no, as that has never been possible (in a reliable fashion) on any version of Android. Plus, Android 10 dramatically limits your access to the filesystem anyway, plus it blocks access to `_DATA` in `MediaStore`. – CommonsWare Aug 30 '19 at 10:47
  • @CommonsWare Will you please guide then how i can upload a file to the server by picking it up from media? Currently i am getting the input stream from URI and then converting it to a file. Thanks in advanced – ice spirit Sep 01 '19 at 19:07
  • 1
    @icespirit: "Currently i am getting the input stream from URI and then converting it to a file" -- copying the bytes to a file that you control is one option. After that, it depends on the particular library or API that you are using to upload files. For example, with Retrofit, you can [use this](https://github.com/square/okhttp/issues/3585#issuecomment-327319196). – CommonsWare Sep 01 '19 at 19:22
  • on Android 10 get such error for file: content://com.android.providers.downloads.documents/document/msf%3A64020 java.lang.NumberFormatException: For input string: "msf:64020" – yozhik Jun 23 '21 at 11:02
  • 1
    if you want to get pdf document Uri add this line `else if ("document".equals(type)){contentUri = MediaStore.Files.getContentUri("external");}` in the getPath method – zoubair Omar May 09 '22 at 13:36
125

Note: This answer addresses part of the problem. For a complete solution (in the form of a library), look at Paul Burke's answer.

You could use the URI to obtain document id, and then query either MediaStore.Images.Media.EXTERNAL_CONTENT_URI or MediaStore.Images.Media.INTERNAL_CONTENT_URI (depending on the SD card situation).

To get document id:

// Will return "image:x*"
String wholeID = DocumentsContract.getDocumentId(uriThatYouCurrentlyHave);

// Split at colon, use second item in the array
String id = wholeID.split(":")[1];

String[] column = { MediaStore.Images.Media.DATA };     

// where id is equal to             
String sel = MediaStore.Images.Media._ID + "=?";

Cursor cursor = getContentResolver().
                          query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
                          column, sel, new String[]{ id }, null);

String filePath = "";

int columnIndex = cursor.getColumnIndex(column[0]);

if (cursor.moveToFirst()) {
    filePath = cursor.getString(columnIndex);
}   

cursor.close();

Reference: I'm not able to find the post that this solution is taken from. I wanted to ask the original poster to contribute here. Will look some more tonight.

Community
  • 1
  • 1
Vikram
  • 51,313
  • 11
  • 93
  • 122
  • That will just give you the unique ID in DocumentsProvider, that has nothing to do with the ID in MediaStore. If you somehow find a matching ID in MediaStore it's most certainly not the corresponding image that DocumentsProvider returned in its URI.. – Magnus Dec 08 '13 at 12:11
  • 7
    @Magnus Thank you for not trying the code above, and sharing your opinion nonetheless. `...that has nothing to do with the ID in MediaStore. If you somehow find a matching ID in MediaStore it's **most certainly** not the corresponding image that DocumentsProvider returned in its URI`. If there was any official documentation that supported my answer, I would have referenced it. But, all I can say is that the code above produced desired output for the few times I tested it. It would have been courteous if you had tried the solution before writing it off as being wrong. – Vikram Dec 09 '13 at 18:29
  • 1
    "Each storage backend surfaces individual files and directories by referencing them with a unique COLUMN_DOCUMENT_ID. Document IDs must be unique and not change once issued, since they are used for persistent URI grants across device reboots." taken from [Storage access framework](https://developer.android.com/guide/topics/providers/document-provider.html) that's the ID you get from `DocumentsContract.getDocumentId` as you can see it has nothing to do with MediaStore, regardless of me being courteous or not – Magnus Dec 09 '13 at 21:31
  • 3
    @Magnus Please look at the source code for [MediaDocumentsProvider](https://android.googlesource.com/platform/packages/providers/MediaProvider/+/android-4.4_r0.9/src/com/android/providers/media/MediaDocumentsProvider.java). The `queryDocument(...)` method uses the document ID (after splitting the `type` and `id` using String#substring(..)) to query `MediaStore.Images.Media.EXTERNAL_CONTENT_URI`. Perhaps, just read the `else if (TYPE_IMAGE.equals(ident.type))` block. – Vikram Dec 10 '13 at 00:09
  • 6
    Yes but the whole idea of the DocumentsProvider is that it abstracts away the underlying data, hence your method is not safe to use, it could be an image in an online service, hence not file uri, hence the method is not safe to use. – Magnus Dec 10 '13 at 11:34
  • This does not work when I attempt to upload an apk from the downloads directory. – Matthew May 09 '14 at 19:44
  • I can't get this line to work: 'DocumentsContract.getDocumentId' My file url is : "file:///storage/emulated/0/cleeck/temp.png" I keep getting "IllegalArgumentException: Not a document..." – Pam May 09 '14 at 20:05
  • 1
    @Matthew AFAIK, an `apk` file is not covered by the `DocumentsContract`. You can check this with `DocumentsContract.isDocumentUri(uri)`. See what `uri.getScheme()` return in your file's case; a document uri will start with `document`. Also, see `Paul Burke's` answer on this page as it covers all possible scenarios. – Vikram May 15 '14 at 16:31
  • 2
    @Pam The URI you have is _not_ a document URI - a document URI starts with `document:`. You can confirm this using `DocumentsContract.isDocumentUri(uri)` which basically checks if `uri` has prefix `"document"`. It seems that you already have a `file` schemed URI. To get file path, you can use `uri.getPath()`. – Vikram May 15 '14 at 16:44
  • 1
    On versions below 10, change: String wholeID = DocumentsContract.getDocumentId(uriThatYouCurrentlyHave); -> if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { id = selectedImageUri.getLastPathSegment(); } else { final String wholeID = selectedImageUri.getLastPathSegment(); id = wholeID.split(":")[1]; } – annelorayne Oct 09 '14 at 20:39
  • 1
    This is a api level 19+ solution... – Loenix Jan 28 '16 at 15:27
  • I am glad I found this solution, since it works on my ancient target.apilevel8 project – Totumus Maximus Dec 06 '17 at 15:54
  • It is not going inside this condition: if (cursor.moveToFirst()) { filePath = cursor.getString(columnIndex); } – Priyanka Singh Jul 06 '20 at 13:26
  • @Magnus It is not going inside this condition: if (cursor.moveToFirst()) { filePath = cursor.getString(columnIndex); } – Priyanka Singh Jul 06 '20 at 13:26
  • @Matthew It is not going inside this condition: if (cursor.moveToFirst()) { filePath = cursor.getString(columnIndex); } – Priyanka Singh Jul 06 '20 at 13:26
77

the answer below is written by https://stackoverflow.com/users/3082682/cvizv on a page which does not exist anymore, since he has not enough rep to answer a question, I am posting it. No credits by me.

public String getImagePath(Uri uri){
   Cursor cursor = getContentResolver().query(uri, null, null, null, null);
   cursor.moveToFirst();
   String document_id = cursor.getString(0);
   document_id = document_id.substring(document_id.lastIndexOf(":")+1);
   cursor.close();

   cursor = getContentResolver().query( 
   android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
   null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
   cursor.moveToFirst();
   String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
   cursor.close();

   return path;
}

Edit: There is a flow on the code; if device has more than one external storage (external sdcard, external usb etc.), above the code won't work non primary storages.

halfer
  • 19,824
  • 17
  • 99
  • 186
guness
  • 6,336
  • 7
  • 59
  • 88
  • 1
    This is the right and correct answer for getting path of image picked from gallery.Thank you very much. I have tried a lot of solution but yours work. Earlier i my code is working using managedQuery but some how i changed api level and managedQuery api is deprecated. So i have to change the code. I search try numbers of solution but nothing work, your solution works perfectly all right. Thanks once again. If i can give two up point i can definitely but system does not allow. – Ahesanali Suthar Mar 19 '15 at 11:17
  • All readers about this problem , just use this solution it works perfect!!! This is the only solution which I found working on Android L and Android M. – Stoycho Andreev Sep 01 '16 at 21:43
  • It gives CursorIndexOutOfBoundsException: Index 0 requested, with a size of 0 – Tushar Gogna Jul 09 '17 at 07:11
  • @TusharGogna it is new, can you share android version and device specifications? – guness Jul 09 '17 at 08:22
  • Nexus 5 running 6.0.1. – Tushar Gogna Jul 09 '17 at 08:41
  • @TusharGogna above code is happy path. please add appropriate checks. `if(cursor.moveToFirst())` etc. My guess is, you URI is not pointing a file. – guness Jul 09 '17 at 17:53
  • God bless you brother, I have suffered a lot to get this. I just replaced every `MediaStore.Images.Media` with `MediaStore.Audio.Media` for audio files – Tosin John Nov 05 '17 at 16:14
  • 1
    Thanks for the solution which actually works. After trying a number of other solutions provided (even with higher votes). This is the one which worked for me. I wonder if there is one general solution which works for all cases so that people don't have to depend upon trial and error way. – abggcv Jan 31 '18 at 09:49
  • Thanks for you solution, it really saved my time – Conan Lee Sep 09 '18 at 10:57
  • What if the file is on the SD Card? When I select a video from the SD Card I get `CursorIndexOutOfBoundsException`. I changed all the `MediaStore.Images.Media.DATA` to `MediaStore.Video.Media.DATA` and I'm using - `intent = new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI);` – ClassA Oct 17 '18 at 11:01
  • 1
    @ClassA probably returned cursor does not have `data` field. maybe it is better to print contents of that cursor. I cannot help without details, please ask a new question for that. – guness Oct 18 '18 at 21:54
  • As other says this is the perfect answer but it did not have much repo it gives the actual path of image. – Husnain Qasim May 09 '19 at 09:19
29

Before new gallery access in KitKat I got my real path in sdcard with this method

That was never reliable. There is no requirement that the Uri that you are returned from an ACTION_GET_CONTENT or ACTION_PICK request has to be indexed by the MediaStore, or even has to represent a file on the file system. The Uri could, for example, represent a stream, where an encrypted file is decrypted for you on the fly.

How could I manage to obtain the real path in sdcard?

There is no requirement that there is a file corresponding to the Uri.

Yes, I really need a path

Then copy the file from the stream to your own temporary file, and use it. Better yet, just use the stream directly, and avoid the temporary file.

I have changed my Intent.ACTION_GET_CONTENT for Intent.ACTION_PICK

That will not help your situation. There is no requirement that an ACTION_PICK response be for a Uri that has a file on the filesystem that you can somehow magically derive.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Commonly we use the path to get Exif tags of the image, e.g. in order determine if the image needs to be rotated for display. By copying the stream to a new file, aren't exif tags lost? I tried writing the bitmap from the inputStream through a FileOutputStream, but I couldn't recover the original exif tags. – ılǝ Dec 06 '13 at 07:56
  • 2
    @ılǝ: "By copying the stream to a new file, aren't exif tags lost?" -- they shouldn't be. Decoding an image to a `Bitmap` will lose the EXIF tags, though. So long as you are keeping it as a `byte[]`, though, and are not attempting any modification of those bytes, I would expect the EXIF tags to survive. – CommonsWare Dec 06 '13 at 13:34
  • @CommonsWare Ok, so you suggest working with URIs instead of trying to get file path. But now with different URI format that will not work `Intent intent = new Intent(Intent.ACTION_VIEW);` `intent.setDataAndType(uri, "video/*");` `startActivity(intent);` – kamil zych Dec 17 '13 at 08:25
  • 1
    This answer is underrated. It should, probably, have mentioned openFileDescriptor() method as well. That said, the existence of ParcelFileDescriptor is rather poorly documented in the first place. – user1643723 May 18 '15 at 19:56
  • this is the right answer but one has to check to ensure the file is not stored locally to avoid keeping duplicates on the user's drive – The-null-Pointer- Oct 18 '15 at 06:51
  • @CommonsWare can you please share a full solution for this with code example ? The accepted answer works but you're right about it being not reliable. – Sharp Edge Sep 04 '16 at 15:40
  • @SharpEdge: There is no single "full solution". It depends on what you are trying to get a "real path" for. There may not even be *any* solution (e.g., get the original file extension). See [this blog post](https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html) for how to consume content from a `Uri`. – CommonsWare Sep 04 '16 at 16:00
  • @CommonsWare All I'm asking for is a simple solution for getting image file from gallery with its path :) Since `onActivityResult` returns Intent. – Sharp Edge Sep 04 '16 at 21:14
  • 4
    @SharpEdge: There is no path. The sooner you stop thinking in terms of files and paths, and instead think in terms of content and `Uri` values, the better off you will be. After all, the contents of a gallery might include files from cloud storage providers (e.g., Google Photos), plus files that you cannot access directly (e.g., on removable storage). Use `ContentResolver` and `DocumentFile`. – CommonsWare Sep 04 '16 at 21:21
  • Ok thanks for the tip on `DocumentFile`. I was using `ContentResolver`, but the issue was that image was not being returned from some apps launched on intent besides the default stock gallery app. – Sharp Edge Sep 04 '16 at 21:26
10

I had the exact same problem. I need the filename so to be able to upload it to a website.

It worked for me, if I changed the intent to PICK. This was tested in AVD for Android 4.4 and in AVD for Android 2.1.

Add permission READ_EXTERNAL_STORAGE :

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Change the Intent :

Intent i = new Intent(
  Intent.ACTION_PICK,
  android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
  );
startActivityForResult(i, 66453666);

/* OLD CODE
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(
  Intent.createChooser( intent, "Select Image" ),
  66453666
  );
*/

I did not have to change my code the get the actual path:

// Convert the image URI to the direct file system path of the image file
 public String mf_szGetRealPathFromURI(final Context context, final Uri ac_Uri )
 {
     String result = "";
     boolean isok = false;

     Cursor cursor = null;
      try { 
        String[] proj = { MediaStore.Images.Media.DATA };
        cursor = context.getContentResolver().query(ac_Uri,  proj, null, null, null);
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        result = cursor.getString(column_index);
        isok = true;
      } finally {
        if (cursor != null) {
          cursor.close();
        }
      }

      return isok ? result : "";
 }
Jan Van Overbeke
  • 250
  • 3
  • 12
  • Your answer is the provisional solution that I have mentioned it. I would like to use the intent with GET_CONTENT and try to make the most of the new picker ui. Although I don't know if it is possible. – Álvaro Dec 09 '13 at 08:45
  • This throws "column '_data' does not exist" exception. – lostintranslation Dec 18 '15 at 16:12
  • @Jan Van Overbeke do we need to specify any MIME type when picking the content? When I test this on 6.0 unfortunately it still opens the DocumentsProvider and not the gallery picker...:( Why? – Edmond Tamas May 25 '16 at 06:45
9

This answer is based on your somewhat vague description. I assume that you fired an intent with action: Intent.ACTION_GET_CONTENT

And now you get content://com.android.providers.media.documents/document/image:62 back instead of the previously media provider URI, correct?

On Android 4.4 (KitKat) the new DocumentsActivity gets opened when an Intent.ACTION_GET_CONTENT is fired thus leading to grid view (or list view) where you can pick an image, this will return the following URIs to calling context (example): content://com.android.providers.media.documents/document/image:62 (these are the URIs to the new document provider, it abstracts away the underlying data by providing generic document provider URIs to clients).

You can however access both gallery and other activities responding to Intent.ACTION_GET_CONTENT by using the drawer in the DocumentsActivity (drag from left to right and you'll see a drawer UI with Gallery to choose from). Just as pre KitKat.

If you still which to pick in DocumentsActivity class and need the file URI, you should be able to do the following (warning this is hacky!) query (with contentresolver):content://com.android.providers.media.documents/document/image:62 URI and read the _display_name value from the cursor. This is somewhat unique name (just the filename on local files) and use that in a selection (when querying) to mediaprovider to get the correct row corresponding to this selection from here you can fetch the file URI as well.

The recommended ways of accessing document provider can be found here (get an inputstream or file descriptor to read file/bitmap):

Examples of using documentprovider

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Magnus
  • 1,483
  • 11
  • 14
  • 2
    Google has just updated the documentation, great. Sorry for my vague description. I want to know the real path from new picker ui to make offline process. I know how to get the name of the file but not the complete path, I would like to obtain this path /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg from this data content://com.android.providers.media.documents/document/image:62 – Álvaro Dec 05 '13 at 09:00
  • I think you are out of luck then. If you're not willing to change to pick intent with mime type image/*, alternatively use the method suggested above (hack). Since google has taken ownership of Intent.ACTION_GET_CONTENT with this new DocumentActivity your only option is to use the methods described in the link above. They seem to think it's enough with the current methods because document provider also wraps cloud services such as Drive etc, then the only option is inputstream back from the document provider (there's no file in that case). – Magnus Dec 05 '13 at 09:17
  • Look at this link: [Columns in document provider](https://developer.android.com/reference/android/provider/DocumentsContract.Document.html) those are the ones available to you once you access the cursor to that document provider, hence you have no file uri option :( – Magnus Dec 05 '13 at 10:45
8

Try this:

//KITKAT
i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, CHOOSE_IMAGE_REQUEST);

Use the following in the onActivityResult:

Uri selectedImageURI = data.getData();
input = c.getContentResolver().openInputStream(selectedImageURI);
BitmapFactory.decodeStream(input , null, opts);
rahulritesh
  • 860
  • 1
  • 9
  • 17
6

Here is an updated version of Paul Burke's answer. In versions below Android 4.4 (KitKat) we don't have the DocumentsContract class.

In order to work on versions below KitKat create this class:

public class DocumentsContract {
    private static final String DOCUMENT_URIS =
        "com.android.providers.media.documents " +
        "com.android.externalstorage.documents " +
        "com.android.providers.downloads.documents " +
        "com.android.providers.media.documents";

    private static final String PATH_DOCUMENT = "document";
    private static final String TAG = DocumentsContract.class.getSimpleName();

    public static String getDocumentId(Uri documentUri) {
        final List<String> paths = documentUri.getPathSegments();
        if (paths.size() < 2) {
            throw new IllegalArgumentException("Not a document: " + documentUri);
        }

        if (!PATH_DOCUMENT.equals(paths.get(0))) {
            throw new IllegalArgumentException("Not a document: " + documentUri);
        }
        return paths.get(1);
    }

    public static boolean isDocumentUri(Uri uri) {
        final List<String> paths = uri.getPathSegments();
        Logger.v(TAG, "paths[" + paths + "]");
        if (paths.size() < 2) {
            return false;
        }
        if (!PATH_DOCUMENT.equals(paths.get(0))) {
            return false;
        }
        return DOCUMENT_URIS.contains(uri.getAuthority());
    }
}
Community
  • 1
  • 1
Danylo Volokh
  • 4,149
  • 2
  • 33
  • 40
4

We need to do the following changes/fixes in our earlier onActivityResult()'s gallery picker code to run seamlessly on Android 4.4 (KitKat) and on all other earlier versions as well.

Uri selectedImgFileUri = data.getData();

if (selectedImgFileUri == null ) {

    // The user has not selected any photo
}

try {

   InputStream input = mActivity.getContentResolver().openInputStream(selectedImgFileUri);
   mSelectedPhotoBmp = BitmapFactory.decodeStream(input);
}
catch (Throwable tr) {

    // Show message to try again
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sachin Gupta
  • 444
  • 3
  • 10