Android Gallery on Android 4.4 (KitKat) returns different URI for Intent.ACTION_GET_CONTENT
Asked Answered
J

21

216

Before KitKat (or before the new Gallery) the Intent.ACTION_GET_CONTENT returned a URI like this

content://media/external/images/media/3951.

Using the ContentResolver and querying for MediaStore.Images.Media.DATA returned the file URL.

In KitKat however the Gallery returns a URI (via "Last") like this:

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

How do I handle this?

Janitor answered 7/11, 2013 at 11:33 Comment(13)
Off the cuff, I would find ways of using the content that does not require direct access to the file. For example, that Uri should be openable as a stream via ContentResolver. I have long been nervous about apps that assume that a content:// Uri that represents a file can always be converted into a File.Colonialism
I think a valid answer is to change to the ContentResolver and work with Uri instead of File-URLs. I will do that. It also enables better handling of non-Gallery Uris.Janitor
@CommonsWare,If I want to save an image path in sqlite db so I can open it later, should I save the URI or absolute file path?Torrid
@Colonialism I agree with your nervousness. :-) However, I need to be able to pass a filename (for an image) to native code. A solution is to copy the data obtained using an InputStream on the ContentResolver to a pre-designated place so it has a known filename. However, this sounds wasteful to me. Any other suggestions?Fellatio
@darrenp: Ummm..., rewrite the native code to work with an InputStream over JNI? There aren't all that many options for you, unfortunately.Colonialism
That's useful to know. Thanks for your response. I've since found out that we are now passing the image to C++ in memory rather than via a file so it we can now use an InputStream instead of a file (which is great). Only EXIF tag reading is slightly tricky and requires Drew Noakes' library. Many thanks for your comments.Fellatio
@Exception there is a bug reported in cordova over this issue so try finder answer it is workingNapalm
@SpryTechies But that works only for images, what about other media types.Grady
@Grady Which media you wants please specifyNapalm
@SpryTechies I mean to say video file.Grady
tagasks.com/…Runyan
better answer is here stackoverflow.com/questions/20067508/… from Paul BurkeSecunderabad
MediaStore.Images.Media.DATA is depricated now and MediaStore.Images.Media._ID with contentWrapperUri always return relative uri not absolute Uri. That is the main problem for getting valid Uri and this Uri not return a valid file path and occurred FileNotFoundException.Edirne
T
109

Try this:

if (Build.VERSION.SDK_INT <19){
    Intent intent = new Intent(); 
    intent.setType("image/jpeg");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.select_picture)),GALLERY_INTENT_CALLED);
} else {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("image/jpeg");
    startActivityForResult(intent, GALLERY_KITKAT_INTENT_CALLED);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode != Activity.RESULT_OK) return;
    if (null == data) return;
    Uri originalUri = null;
    if (requestCode == GALLERY_INTENT_CALLED) {
        originalUri = data.getData();
    } else if (requestCode == GALLERY_KITKAT_INTENT_CALLED) {
        originalUri = data.getData();
        final int takeFlags = data.getFlags()
                & (Intent.FLAG_GRANT_READ_URI_PERMISSION
                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        // Check for the freshest data.
        getContentResolver().takePersistableUriPermission(originalUri, takeFlags);
    }

    loadSomeStreamAsynkTask(originalUri);

}

Probably need

@SuppressLint("NewApi")

for

takePersistableUriPermission

Tocci answered 9/11, 2013 at 9:57 Comment(8)
Would you mind to elaborate what the KitKat code is doing? Does this require any new permission? The pre KitKat code works for me on KitKat, too. So why would I choose to use a different code for KitKat? Thanks.Janitor
My old code refused to work on KitKat. I had to read the manual. Well, that new permissions are not required.Tocci
it seems we can't get path from new sdks uri. Also it is a shame google made this kind of change without proper documantation and announcement.Omsk
Could you explain how to get the file URL? I would like to obtain the real path in sdcard. For example, if it is a picture, I would like to obtain this path /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg instead of the document Uri.Hundredfold
Based on KitKat docs (developer.android.com/about/versions/…) this may not be what the OP needs unless he does intent to use/edit documents that are owned by the other application(s). If the OP wants a copy or to do things in a way consistent with older versions, the answer by @voytez would be more appropriate.Premillennial
This isn't working for me. I get the following exception (on stock 4.4.2): E/AndroidRuntime(29204): Caused by: java.lang.SecurityException: Requested flags 0x1, but only 0x0 are allowedPteryla
if data.getData() is null, use: bitmap = (Bitmap)data.getExtras().get("data");Quarrelsome
I got on Samsung s7, nougat. SecurityException: No persistable permission grants found for UID 10206 and Uri 0 @ content://com.android.providers.media.documents/document/image:958Nason
M
180

This requires no special permissions, and works with the Storage Access Framework, as well as the unofficial ContentProvider pattern (file path in _data field).

/**
 * 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 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();
    }

    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;
}


/**
 * @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());
}

/**
 * @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());
}

See an up-to-date version of this method here.

Mysia answered 13/12, 2013 at 5:2 Comment(23)
This worked fantastically on a 4.4 Nexus 5 Documents UI and some other pref KitKat devices using the standard gallery apps, thanks Paul!Seaward
Thanks for this, it's taken me ages to get this far with sdk 19!! My problem is that my device is using Google drive as file browser. If the file is on the device image path is got fine but if the file is on the drive it does not open. Maybe I just need to look at dealing with opening images from google drive. Thing is my app is written to use file path and get image using insampling...Hex
@Hex When you select a Drive file, it gives back Authority: com.google.android.apps.docs.storage and Segments: [document, acc=1;doc=667]. I'm not sure, but assume that the doc value is the Uri ID that you can query against. You'll likely need permissions to be set up as detailed in "Authorize your app on Android" here: developers.google.com/drive/integrate-android-ui. Please update here if you figure it out.Mysia
Thanks, I thought if they chose to use the Drive it would do all the downloading etc. and just give me the file. As I wan't using it to upload. Seems a but odd to have to program for it. Thanks againHex
Hey, this so far has been working really well, thanks a lot! Did the Google Drive issue ever get sorted out?Kempe
i'm facing the same problem using File plugin the value is the id not the actual path, can i use the same solution? and tell me please how to call this method from file plugin class, it's urgent matter for me :(Palaearctic
I like that your code is checkstyle compliant other than missing @return in the initial method javadoc (and a few other minor tidbits). Kudos, sir! Note that it appeared to require 4.4 also, so I marked it with the annotation @TargetApi(Build.VERSION_CODES.KITKAT) - oh and lastly, it is pleasing to note that someone other than myself uses "final" heavily. :-)Emmuela
This solution works wonderfull ! thanks for that. @Hex i'm wondering if the Google Drive problem has been fixed! I needed myself too :)Serenity
this is absolutely horrible! you should not continue to propagate code that "cheats" like this. it only supports the source apps that you know the pattern for, and the whole point of the document provider model is to support arbitrary sourcesTokenism
By using if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) are you assuming that if isKitKatis false, than DocumentsContract.isDocumentUri(context, uri) won't be checked (reasonable thinking) ? if so, then how can you be sure about that? if not, then shouldn't the if (isKitKat) be checked first, then if (DocumentsContract.isDocumentUri(context, uri)) ? @Paul BurkeSturdivant
Workaround for new Google Photos app gist.github.com/alexzaitsev/75c9b36dfcffd545c676Princeling
@PaulBurke : How to get pdf or text file from google drive using this code ?Please help me as soon as possibleResidence
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?Euell
@user2162550 consider if (a() && b()). In this case, if a is false, b will not be checked by JVM since there is no point checking for b -- result is already false.Bickart
Hi, if I use the above code, together with a 3rd party file explorer, null will returned eventually. I was wondering, is this is shortcoming of the above code, or incorrect implementation of 3rd party file explorer app? Thanks - https://mcmap.net/q/24454/-in-action_get_content-unable-to-resolve-uri-to-full-file-path-if-3rd-party-file-explorer-app-is-used/72437Pend
May 2016 and still working on marshmallow with a min target of api 15 .Great jobRheinland
>Get the value of the data column for this Uri. This is useful for MediaStore Uris, and other file-based ContentProviders. What is the purpose of getDataColumn? What does it do?Oleum
>// TODO handle non-primary volumes. Does it work with external storage like external sd cards?Oleum
The _data wouldn't work when ContentProvider doesn't support it. It is recommended to follow @Colonialism instructions and not use full file path anymore, because it could be a file in Dropbox cloud instead of a real file.Tipi
I had this in my app and now it breaks on Android 7+ nougat devicesAthapaskan
For those who are getting Error Unknown URI: content://downloads/public_downloads https://mcmap.net/q/18044/-unknown-url-content-downloads-my_downloadsDissever
@Hex did u find any way to pick file from drive and upload it to server??Trula
@j_m - agreed with your point (from several years ago). I'm seeing ids like msf:31 from the emulator running API 30 for some images I've uploaded (drag and dropped) to it, which is not handled by this code. What is your suggestion then for the correct approach to the OPs problem, since this is brittle as you suggest?Skeie
T
109

Try this:

if (Build.VERSION.SDK_INT <19){
    Intent intent = new Intent(); 
    intent.setType("image/jpeg");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.select_picture)),GALLERY_INTENT_CALLED);
} else {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("image/jpeg");
    startActivityForResult(intent, GALLERY_KITKAT_INTENT_CALLED);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode != Activity.RESULT_OK) return;
    if (null == data) return;
    Uri originalUri = null;
    if (requestCode == GALLERY_INTENT_CALLED) {
        originalUri = data.getData();
    } else if (requestCode == GALLERY_KITKAT_INTENT_CALLED) {
        originalUri = data.getData();
        final int takeFlags = data.getFlags()
                & (Intent.FLAG_GRANT_READ_URI_PERMISSION
                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        // Check for the freshest data.
        getContentResolver().takePersistableUriPermission(originalUri, takeFlags);
    }

    loadSomeStreamAsynkTask(originalUri);

}

Probably need

@SuppressLint("NewApi")

for

takePersistableUriPermission

Tocci answered 9/11, 2013 at 9:57 Comment(8)
Would you mind to elaborate what the KitKat code is doing? Does this require any new permission? The pre KitKat code works for me on KitKat, too. So why would I choose to use a different code for KitKat? Thanks.Janitor
My old code refused to work on KitKat. I had to read the manual. Well, that new permissions are not required.Tocci
it seems we can't get path from new sdks uri. Also it is a shame google made this kind of change without proper documantation and announcement.Omsk
Could you explain how to get the file URL? I would like to obtain the real path in sdcard. For example, if it is a picture, I would like to obtain this path /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg instead of the document Uri.Hundredfold
Based on KitKat docs (developer.android.com/about/versions/…) this may not be what the OP needs unless he does intent to use/edit documents that are owned by the other application(s). If the OP wants a copy or to do things in a way consistent with older versions, the answer by @voytez would be more appropriate.Premillennial
This isn't working for me. I get the following exception (on stock 4.4.2): E/AndroidRuntime(29204): Caused by: java.lang.SecurityException: Requested flags 0x1, but only 0x0 are allowedPteryla
if data.getData() is null, use: bitmap = (Bitmap)data.getExtras().get("data");Quarrelsome
I got on Samsung s7, nougat. SecurityException: No persistable permission grants found for UID 10206 and Uri 0 @ content://com.android.providers.media.documents/document/image:958Nason
L
68

Had the same problem, tried the solution above but though it worked generally, for some reason I was getting permission denial on Uri content provider for some images although I had the android.permission.MANAGE_DOCUMENTS permission added properly.

Anyway found other solution which is to force opening image gallery instead of KITKAT documents view with :

// KITKAT

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

and then load the image:

Uri selectedImageURI = data.getData();
input = c.getContentResolver().openInputStream(selectedImageURI);
                BitmapFactory.decodeStream(input , null, opts);

EDIT

ACTION_OPEN_DOCUMENT might require you to persist permissions flags etc and generally often results in Security Exceptions...

Other solution is to use the ACTION_GET_CONTENT combined with c.getContentResolver().openInputStream(selectedImageURI) which will work both on pre-KK and KK. Kitkat will use new documents view then and this solution will work with all apps like Photos, Gallery, File Explorer, Dropbox, Google Drive etc...) but remember that when using this solution you have to create image in your onActivityResult() and store it on SD Card for example. Recreating this image from saved uri on next app launch would throw Security Exception on content resolver even when you add permission flags as described in Google API docs (that's what happened when I did some testing)

Additionally the Android Developer API Guidelines suggest:

ACTION_OPEN_DOCUMENT is not intended to be a replacement for ACTION_GET_CONTENT. The one you should use depends on the needs of your app:

Use ACTION_GET_CONTENT if you want your app to simply read/import data. With this approach, the app imports a copy of the data, such as an image file.

Use ACTION_OPEN_DOCUMENT if you want your app to have long term, persistent access to documents owned by a document provider. An example would be a photo-editing app that lets users edit images stored in a document provider.

Lightish answered 24/11, 2013 at 17:34 Comment(7)
This answer contained the right information for my purposes. Conditionally using the ACTION_PICK and EXTERNAL_CONTENT_URI on KitKat provided the same ability to get meta-data about Images in the gallery via ContentResolver like is possible on older versions using simply ACTION_GET_CONTENT.Premillennial
@voytez, Can this URI returned through your message converted to the full real path of the image?Torrid
I believe so, it should work just like before KitKat as this code forces opening image gallery instead of KK documents view. But if you intend to use it to create image then this solution is better as converting to real path is an extra unneccessary step.Lightish
Worked for me too, instead of Intent.ACTION_GET_CONTENT. Anyway I kept the Intent.createChooser() wrapper on the new Intent, to let user choose the app for browsing, and worked as expected. Can someone see drawbacks for this solution?Ogdan
Could you clarify if the ACTION_PICK / EXTERNAL_CONTENT_URI intent call works in pre-KitKat, such as 4.3 (API 18)?Namnama
what if I need to pick an audio file?Kulsrud
For anyone who's wondering the quote comes from developer.android.com/guide/topics/providers/…Remodel
E
39

Just as Commonsware mentioned, you shouldn't assume, that the stream you get via ContentResolver is convertable into file.

What you really should do is to open the InputStream from the ContentProvider, then create a Bitmap out of it. And it works on 4.4 and earlier versions as well, no need for reflection.

    //cxt -> current context

    InputStream input;
    Bitmap bmp;
    try {
        input = cxt.getContentResolver().openInputStream(fileUri);
        bmp = BitmapFactory.decodeStream(input);
    } catch (FileNotFoundException e1) {

    }

Of course if you handle big images, you should load them with appropriate inSampleSize: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html. But that's another topic.

Envelopment answered 22/11, 2013 at 13:35 Comment(10)
This isn't working for me with a Nexus 4 running Kitkat but it is with a Galaxy S3 running 4.1.2Sapphera
@Sapphera which part is not working? Do you get any exception?Arvonio
Turned out I was using the wrong intent call to the gallery. I was using one that was for any kind of documents, but with a file extension filter.Sapphera
What a beautifully simple answer, thank you! For others following this answer 'cxt' refers to the current context and will usually be 'this'.Scratchboard
Thanks, I'll edit my answer so it's clear what cxt isArvonio
This doesn't work for me, I get FileNotFoundException calling openInputStream on a LG Nexus 5 with Android 4.4.4 on a uri like this: content://com.android.providers.downloads.documents/document/532Melvin
This probably means that the file just isn't there. The URI seems OK.Arvonio
Thank you so much, its working, you just made my day .Fogg
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getActivity().getContentResolver(), uri). What is wrong with this?Oleum
How can use this input to write EXIF data to image versions before Android 7.1. ? In Android 7.1 ExifInterface can use inputStream but in versions before that ExifInterface only takes String as an argumentBonaventura
E
34

I believe the responses already posted should get people going in the right direction. However here is what I did that made sense for the legacy code I was updating. The legacy code was using the URI from the gallery to change and then save the images.

Prior to 4.4 (and google drive), the URIs would look like this: content://media/external/images/media/41

As stated in the question, they more often look like this: content://com.android.providers.media.documents/document/image:3951

Since I needed the ability to save images and not disturb the already existing code, I just copied the URI from the gallery into the data folder of the app. Then originated a new URI from the saved image file in the data folder.

Here's the idea:

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent), CHOOSE_IMAGE_REQUEST);

public void onActivityResult(int requestCode, int resultCode, Intent data) {

    super.onActivityResult(requestCode, resultCode, data);

    File tempFile = new File(this.getFilesDir().getAbsolutePath(), "temp_image");

    //Copy URI contents into temporary file.
    try {
        tempFile.createNewFile();
        copyAndClose(this.getContentResolver().openInputStream(data.getData()),new FileOutputStream(tempFile));
    }
    catch (IOException e) {
        //Log Error
    }

    //Now fetch the new URI
    Uri newUri = Uri.fromFile(tempFile);

    /* Use new URI object just like you used to */
 }

Note - copyAndClose() just does file I/O to copy InputStream into a FileOutputStream. The code is not posted.

Earmuff answered 29/1, 2014 at 22:14 Comment(4)
pretty clever. i too needed the actual file uriMasurium
you are my hero, this is exactly what I needed! works great for Google Drive files as wellResponse
Think out of the box, right? :D This code works exactly like I was expecting.Carsoncarstensz
post the code for copyAndClose, the answer is not completeKilometer
A
31

Just wanted to say that this answer is brilliant and I'm using it for a long time without problems. But some time ago I've stumbled upon a problem that DownloadsProvider returns URIs in format content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdf and hence app is crashed with NumberFormatException as it's impossible to parse its uri segments as long. But raw: segment contains direct uri which can be used to retrieve a referenced file. So I've fixed it by replacing isDownloadsDocument(uri) if content with following:

final String id = DocumentsContract.getDocumentId(uri);
if (!TextUtils.isEmpty(id)) {
if (id.startsWith("raw:")) {
    return id.replaceFirst("raw:", "");
}
try {
    final Uri contentUri = ContentUris.withAppendedId(
            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
    return getDataColumn(context, contentUri, null, null);
} catch (NumberFormatException e) {
    Log.e("FileUtils", "Downloads provider returned unexpected uri " + uri.toString(), e);
    return null;
}
}
Allcot answered 29/9, 2017 at 8:43 Comment(3)
Works perfect! Thank YouTrireme
Perfecto, Thx @AllcotNoland
Perefect WorkingGnomon
Z
8

I've combine multiple answers into one working solution that results with file path

Mime type is irrelevant for the example purpose.

            Intent intent;
            if(Build.VERSION.SDK_INT >= 19){
                intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
                intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
            }else{
                intent = new Intent(Intent.ACTION_GET_CONTENT);
            }
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setType("application/octet-stream");
            if(isAdded()){
                startActivityForResult(intent, RESULT_CODE);
            }

Handling result

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if( requestCode == RESULT_CODE && resultCode == Activity.RESULT_OK) {
        Uri uri = data.getData();
        if (uri != null && !uri.toString().isEmpty()) {
            if(Build.VERSION.SDK_INT >= 19){
                final int takeFlags = data.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION;
                //noinspection ResourceType
                getActivity().getContentResolver()
                        .takePersistableUriPermission(uri, takeFlags);
            }
            String filePath = FilePickUtils.getSmartFilePath(getActivity(), uri);
            // do what you need with it...
        }
    }
}

FilePickUtils

import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;

public class FilePickUtils {
    private static String getPathDeprecated(Context ctx, Uri uri) {
        if( uri == null ) {
            return null;
        }
        String[] projection = { MediaStore.Images.Media.DATA };
        Cursor cursor = ctx.getContentResolver().query(uri, projection, null, null, null);
        if( cursor != null ){
            int column_index = cursor
                    .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            return cursor.getString(column_index);
        }
        return uri.getPath();
    }

    public static String getSmartFilePath(Context ctx, Uri uri){

        if (Build.VERSION.SDK_INT < 19) {
            return getPathDeprecated(ctx, uri);
        }
        return  FilePickUtils.getPath(ctx, uri);
    }

    @SuppressLint("NewApi")
    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());
    }

}
Zipporah answered 5/5, 2015 at 10:12 Comment(2)
i was facing issue .... uri.getPath() was returning uri with /external and it was not returning path. i added this else if ("content".equalsIgnoreCase(uri.getScheme())) block and this works good now .. can u explain what does it doUnsound
filePath is getting null.. In uri: content://com.android.externalstorage.documents/document/799B-1419%3AScreenshot%2FScreenshot_20181117_162826.pngChalcopyrite
S
8

Question

How to get an actual file path from a URI

Answer

To my knowledge, we don't need to get the file path from a URI because for most of the cases we can directly use the URI to get our work done (like 1. getting bitmap 2. Sending a file to the server, etc.)

1. Sending to the server

We can directly send the file to the server using just the URI.

Using the URI we can get InputStream, which we can directly send to the server using MultiPartEntity.

Example

/**
 * Used to form Multi Entity for a URI (URI pointing to some file, which we got from other application).
 *
 * @param uri     URI.
 * @param context Context.
 * @return Multi Part Entity.
 */
public MultipartEntity formMultiPartEntityForUri(final Uri uri, final Context context) {
    MultipartEntity multipartEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, Charset.forName("UTF-8"));
    try {
        InputStream inputStream = mContext.getContentResolver().openInputStream(uri);
        if (inputStream != null) {
            ContentBody contentBody = new InputStreamBody(inputStream, getFileNameFromUri(uri, context));
            multipartEntity.addPart("[YOUR_KEY]", contentBody);
        }
    }
    catch (Exception exp) {
        Log.e("TAG", exp.getMessage());
    }
    return multipartEntity;
}

/**
 * Used to get a file name from a URI.
 *
 * @param uri     URI.
 * @param context Context.
 * @return File name from URI.
 */
public String getFileNameFromUri(final Uri uri, final Context context) {

    String fileName = null;
    if (uri != null) {
        // Get file name.
        // File Scheme.
        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
            File file = new File(uri.getPath());
            fileName = file.getName();
        }
        // Content Scheme.
        else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
            Cursor returnCursor =
                    context.getContentResolver().query(uri, null, null, null, null);
            if (returnCursor != null && returnCursor.moveToFirst()) {
                int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                fileName = returnCursor.getString(nameIndex);
                returnCursor.close();
            }
        }
    }
    return fileName;
}

2. Getting a BitMap from a URI

If the URI is pointing to image then we will get bitmap, else null:

/**
 * Used to create bitmap for the given URI.
 * <p>
 * 1. Convert the given URI to bitmap.
 * 2. Calculate ratio (depending on bitmap size) on how much we need to subSample the original bitmap.
 * 3. Create bitmap bitmap depending on the ration from URI.
 * 4. Reference - https://mcmap.net/q/18045/-how-to-get-bitmap-from-an-uri
 *
 * @param context       Context.
 * @param uri           URI to the file.
 * @param bitmapSize Bitmap size required in PX.
 * @return Bitmap bitmap created for the given URI.
 * @throws IOException
 */
public static Bitmap createBitmapFromUri(final Context context, Uri uri, final int bitmapSize) throws IOException {

    // 1. Convert the given URI to bitmap.
    InputStream input = context.getContentResolver().openInputStream(uri);
    BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
    onlyBoundsOptions.inJustDecodeBounds = true;
    onlyBoundsOptions.inDither = true;//optional
    onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
    BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
    input.close();
    if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
        return null;
    }

    // 2. Calculate ratio.
    int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth;
    double ratio = (originalSize > bitmapSize) ? (originalSize / bitmapSize) : 1.0;

    // 3. Create bitmap.
    BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
    bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
    bitmapOptions.inDither = true;//optional
    bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
    input = context.getContentResolver().openInputStream(uri);
    Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
    input.close();

    return bitmap;
}

/**
 * For Bitmap option inSampleSize - We need to give value in power of two.
 *
 * @param ratio Ratio to be rounded of to power of two.
 * @return Ratio rounded of to nearest power of two.
 */
private static int getPowerOfTwoForSampleRatio(final double ratio) {
    int k = Integer.highestOneBit((int) Math.floor(ratio));
    if (k == 0) return 1;
    else return k;
}

Comments

  1. Android doesn't provide any methods to get file path from a URI, and in most of the above answers we have hard coded some constants, which may break in feature release (sorry, I may be wrong).
  2. Before going directly going to a solution of the getting file path from a URI, try if you can solve your use case with a URI and Android default methods.

Reference

  1. https://developer.android.com/guide/topics/providers/content-provider-basics.html
  2. https://developer.android.com/reference/android/content/ContentResolver.html
  3. https://hc.apache.org/httpcomponents-client-ga/httpmime/apidocs/org/apache/http/entity/mime/content/InputStreamBody.html
Stuck answered 25/8, 2016 at 12:40 Comment(1)
Thank you. Using the Uri and ContentResolver this way simplified my application significantly when dealing with files.Illuminator
G
7

This Android library handles the case changes in KitKat(including the oldere versions - 2.1+):
https://github.com/iPaulPro/aFileChooser

Use the String path = FileUtils.getPath(context, uri) to convert the returned Uri to a path string useable on all OS version. See more about it here: https://mcmap.net/q/18043/-get-real-path-from-uri-android-kitkat-new-storage-access-framework-duplicate

Germinate answered 1/11, 2014 at 15:11 Comment(0)
L
3

For those who is still using @Paul Burke's code with Android SDK version 23 and above, if your project met the error saying that you are missing EXTERNAL_PERMISSION, and you are very sure you have already added user-permission in your AndroidManifest.xml file. That's because you may in Android API 23 or above and Google make it necessary to guarantee permission again while you make the action to access the file in runtime.

That means: If your SDK version is 23 or above, you are asked for READ & WRITE permission while you are selecting the picture file and want to know the URI of it.

And following is my code, in addition to Paul Burke's solution. I add these code and my project start to work fine.

private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static final String[] PERMISSINOS_STORAGE = {
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
};

public static void verifyStoragePermissions(Activity activity) {
    int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);

    if (permission != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(
                activity,
                PERMISSINOS_STORAGE,
                REQUEST_EXTERNAL_STORAGE
        );
    }
}

And in your activity&fragment where you are asking for the URI:

private void pickPhotoFromGallery() {

    CompatUtils.verifyStoragePermissions(this);
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    // startActivityForResult(intent, REQUEST_PHOTO_LIBRARY);
    startActivityForResult(Intent.createChooser(intent, "选择照片"),
            REQUEST_PHOTO_LIBRARY);
}

In my case, CompatUtils.java is where I define the verifyStoragePermissions method (as static type so I can call it within other activity).

Also it should make more sense if you make an if state first to see whether the current SDK version is above 23 or not before you call the verifyStoragePermissions method.

Labored answered 24/11, 2015 at 12:6 Comment(0)
B
2

This is what I do:

Uri selectedImageURI = data.getData();    imageFile = new File(getRealPathFromURI(selectedImageURI)); 

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

NOTE: managedQuery() method is deprecated, so I am not using it.

This answer is from m3n0R on question android get real path by Uri.getPath() and I claim no credit. I just thought that people who haven't solved this issue yet could use this.

Beilul answered 17/1, 2014 at 20:2 Comment(3)
This is not an answer to new Gallery app (strictly "Media Documents Provider" app) on KitKat.Embolus
The app that questioner calls "Gallery" is probably new file picker on kitkat. FYI: addictivetips.com/android/…Embolus
I do similar and got IndexOutOfBound on Nexus 5X, Android 6 on this line: cursor.getString(idx);Jibber
X
2

If anyone's interested, I made a working Kotlin version for ACTION_GET_CONTENT:

var path: String = uri.path // uri = any content Uri
val databaseUri: Uri
val selection: String?
val selectionArgs: Array<String>?
if ("/document/image:" in path || "/document/image%3A" in path) {
    // files selected from "Documents"
    databaseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    selection = "_id=?"
    selectionArgs = arrayOf(DocumentsContract.getDocumentId(uri).split(":")[1])
} else { // files selected from all other sources, especially on Samsung devices
    databaseUri = uri
    selection = null
    selectionArgs = null
}
try {
    val projection = arrayOf(MediaStore.Images.Media.DATA,
        MediaStore.Images.Media._ID,
        MediaStore.Images.Media.ORIENTATION,
        MediaStore.Images.Media.DATE_TAKEN) // some example data you can query
    val cursor = context.contentResolver.query(databaseUri,
        projection, selection, selectionArgs, null)
    if (cursor.moveToFirst()) {
        // do whatever you like with the data
    }
    cursor.close()
} catch (e: Exception) {
    Log.e(TAG, e.message, e)
}
Xenolith answered 8/3, 2017 at 6:33 Comment(1)
I just want a working code of kotlin. It's work for me. thanksTriumph
R
2

I've tried several of the answers here, and I think I have a solution that will work every time and manages permissions as well.

It is based on the clever solution from LEO. This post should contain all the code you need to make this work, and it should work on any phone and Android version ;)

In order to have the ability to pick a file from an SD card, you'll need this in your manifest:

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

Constants:

private static final int PICK_IMAGE = 456; // Whatever number you like
public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL = 28528; // Whatever number you like
public static final String FILE_TEMP_NAME = "temp_image"; // Whatever file name you like

Check permission and launchImagePick if possible

if (ContextCompat.checkSelfPermission(getThis(),
        Manifest.permission.READ_EXTERNAL_STORAGE)
        != PackageManager.PERMISSION_GRANTED) {

    ActivityCompat.requestPermissions(getThis(),
            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
            MY_PERMISSIONS_REQUEST_READ_EXTERNAL);
}
else {
    launchImagePick();
}

Permission response

@Override
public void onRequestPermissionsResult(int requestCode,
                                       @NonNull
                                         String permissions[],
                                       @NonNull
                                         int[] grantResults) {

    if (manageReadExternalPermissionResponse(this, requestCode, grantResults)) {
        launchImagePick();
    }
}

Manage permission response

public static boolean manageReadExternalPermissionResponse(final Activity activity, int requestCode, int[] grantResults) {

    if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL) {

        // If request is cancelled, the result arrays are empty.

        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            // Permission was granted, yay! Do the
            // contacts-related task you need to do.
            return true;

        } else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED) {

            boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity,
                    Manifest.permission.READ_EXTERNAL_STORAGE);

            if (!showRationale) {
                // The user also CHECKED "never ask again".
                // You can either enable some fall back,
                // disable features of your app
                // or open another dialog explaining
                // again the permission and directing to
                // the app setting.

            } else {
                // The user did NOT check "never ask again".
                // This is a good place to explain the user
                // why you need the permission and ask if he/she wants
                // to accept it (the rationale).
            }
        } else {
            // Permission denied, boo! Disable the
            // functionality that depends on this permission.
        }
    }
    return false;
}

Launch image pick

private void launchImagePick() {

    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    startActivityForResult(intent, PICK_IMAGE);

    // see onActivityResult
}

Manage Image pick response

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == PICK_IMAGE) {

        if (resultCode == Activity.RESULT_OK) {
            if (data != null && data.getData() != null) {

                try {
                     InputStream inputStream = getContentResolver().openInputStream(data.getData())
                     if (inputStream != null) {

                        // No special persmission needed to store the file like that
                        FileOutputStream fos = openFileOutput(FILE_TEMP_NAME, Context.MODE_PRIVATE);

                        final int BUFFER_SIZE = 1 << 10 << 3; // 8 KiB buffer
                        byte[] buffer = new byte[BUFFER_SIZE];
                        int bytesRead = -1;
                        while ((bytesRead = inputStream.read(buffer)) > -1) {
                            fos.write(buffer, 0, bytesRead);
                        }
                        inputStream.close();
                        fos.close();

                        File tempImageFile = new File(getFilesDir()+"/"+FILE_TEMP_NAME);

                        // Do whatever you want with the File

                        // Delete when not needed anymore
                        deleteFile(FILE_TEMP_NAME);
                    }
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                // Error display
            }
        } else {
            // The user did not select any image
        }
    }
}

That's all folks; this works for me on all the telephones I have.

Recital answered 16/3, 2017 at 15:56 Comment(0)
F
1

Please try to avoid using takePersistableUriPermission method because it raised runtime exception for me. /** * Select from gallery. */

public void selectFromGallery() {
    if (Build.VERSION.SDK_INT < AppConstants.KITKAT_API_VERSION) {

        Intent intent = new Intent(); 
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        ((Activity)mCalledContext).startActivityForResult(intent,AppConstants.GALLERY_INTENT_CALLED);

    } else {

        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        ((Activity)mCalledContext).startActivityForResult(intent, AppConstants.GALLERY_AFTER_KITKAT_INTENT_CALLED);
    }
}

OnActivity for result to handle the image data:

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    //gallery intent result handling before kit-kat version
    if(requestCode==AppConstants.GALLERY_INTENT_CALLED 
            && resultCode == RESULT_OK) {

        Uri selectedImage = data.getData();
        String[] filePathColumn = {MediaStore.Images.Media.DATA};
        Cursor cursor = getContentResolver().query(selectedImage,filePathColumn, null, null, null);
        cursor.moveToFirst();
        int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
        String filePath = cursor.getString(columnIndex);
        cursor.close();
        photoFile = new File(filePath);
        mImgCropping.startCropImage(photoFile,AppConstants.REQUEST_IMAGE_CROP);

    }
    //gallery intent result handling after kit-kat version
    else if (requestCode == AppConstants.GALLERY_AFTER_KITKAT_INTENT_CALLED 
            && resultCode == RESULT_OK) {

        Uri selectedImage = data.getData();
        InputStream input = null;
        OutputStream output = null;

        try {
            //converting the input stream into file to crop the 
            //selected image from sd-card.
            input = getApplicationContext().getContentResolver().openInputStream(selectedImage);
            try {
                photoFile = mImgCropping.createImageFile();
            } catch (IOException e) {
                e.printStackTrace();
            }catch(Exception e) {
                e.printStackTrace();
            }
            output = new FileOutputStream(photoFile);

            int read = 0;
            byte[] bytes = new byte[1024];

            while ((read = input.read(bytes)) != -1) {
                try {
                    output.write(bytes, 0, read);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
Fearfully answered 4/1, 2015 at 19:26 Comment(3)
Where are you displaying the image in second case?Source
sorry i failed to add this line of code in else if mImgCropping.startCropImage(photoFile,AppConstants.REQUEST_IMAGE_CROP); again need to call the imagecropping function as per my project needFearfully
What file type is photoFile and mImgCropping?Froward
R
0

This is a total hack, but here's what I did...

So while playing with setting up a DocumentsProvider, I noticed that the sample code (in getDocIdForFile, around line 450) generates a unique id for a selected document based on the file's (unique) path relative to the specified root you give it (that is, what you set mBaseDir to on line 96).

So the URI ends up looking something like:

content://com.example.provider/document/root:path/to/the/file

As the docs say, it's assuming only a single root (in my case that's Environment.getExternalStorageDirectory() but you may use somewhere else... then it takes the file path, starting at the root, and makes it the unique ID, prepending "root:". So I can determine the path by eliminating the "/document/root:" part from uri.getPath(), creating an actual file path by doing something like this:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
// check resultcodes and such, then...
uri = data.getData();
if (uri.getAuthority().equals("com.example.provider"))  {
    String path = Environment.getExternalStorageDirectory(0.toString()
                 .concat("/")
                 .concat(uri.getPath().substring("/document/root:".length())));
    doSomethingWithThePath(path); }
else {
    // another provider (maybe a cloud-based service such as GDrive)
    // created this uri.  So handle it, or don't.  You can allow specific
    // local filesystem providers, filter non-filesystem path results, etc.
}

I know. It's shameful, but it worked. Again, this relies on you using your own documents provider in your app to generate the document ID.

(Also, there's a better way to build the path that don't assume "/" is the path separator, etc. But you get the idea.)

Rickets answered 26/11, 2013 at 6:16 Comment(1)
To reply to myself with an even crazier thought-- if your app is already handling file:// intents from an external file picker, you could also check the authority, as in the example above to make sure it's from your custom provider, and if so you could also use the path to "forge" a new file:// intent using the path you extracted, then StartActivity() and let your app pick it up. I know, terrible.Rickets
T
0

This worked fine for me:

else if(requestCode == GALLERY_ACTIVITY_NEW && resultCode == Activity.RESULT_OK)
{
    Uri uri = data.getData();
    Log.i(TAG, "old uri =  " + uri);
    dumpImageMetaData(uri);

    try {
        ParcelFileDescriptor parcelFileDescriptor =
                getContentResolver().openFileDescriptor(uri, "r");
        FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
        Log.i(TAG, "File descriptor " + fileDescriptor.toString());

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        options.inSampleSize =
           BitmapHelper.calculateInSampleSize(options,
                                              User.PICTURE_MAX_WIDTH_IN_PIXELS,
                                              User.PICTURE_MAX_HEIGHT_IN_PIXELS);
        options.inJustDecodeBounds = false;

        Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
        imageViewPic.setImageBitmap(bitmap);

        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        // get byte array here
        byte[] picData = stream.toByteArray();
        ParseFile picFile = new ParseFile(picData);
        user.setProfilePicture(picFile);
    }
    catch(FileNotFoundException exc)
    {
        Log.i(TAG, "File not found: " + exc.toString());
    }
}
Thorman answered 6/10, 2015 at 13:56 Comment(1)
forget the dumpImageMetaData(uri); it is unnecessaryThorman
S
0

Building up on Paul Burke's answer I faced many problems resolving external SD card's URI path as most of the suggested "built-in" functions return paths which do not get resolved to files.

However, this is my approach of his // TODO handle non-primary volumes.

String resolvedPath = "";
File[] possibleExtSdComposites = context.getExternalFilesDirs(null);
for (File f : possibleExtSdComposites) {
    // Reset final path
    resolvedPath = "";

    // Construct list of folders
    ArrayList<String> extSdSplit = new ArrayList<>(Arrays.asList(f.getPath().split("/")));

    // Look for folder "<your_application_id>"
    int idx = extSdSplit.indexOf(BuildConfig.APPLICATION_ID);

    // ASSUMPTION: Expected to be found at depth 2 (in this case ExtSdCard's root is /storage/0000-0000/) - e.g. /storage/0000-0000/Android/data/<your_application_id>/files
    ArrayList<String> hierarchyList = new ArrayList<>(extSdSplit.subList(0, idx - 2));

    // Construct list containing full possible path to the file
    hierarchyList.add(tail);
    String possibleFilePath = TextUtils.join("/", hierarchyList);

    // If file is found --> success
    if (idx != -1 && new File(possibleFilePath).exists()) {
        resolvedPath = possibleFilePath;
        break;
    }
}

if (!resolvedPath.equals("")) {
    return resolvedPath;
} else {
    return null;
}

Note it depends on hierarchy which might be different on every phone manufacturer - I have not tested them all (it worked well so far on Xperia Z3 API 23 and Samsung Galaxy A3 API 23).

Please confirm if it does not perform well elsewhere.

Silda answered 6/6, 2017 at 7:0 Comment(0)
M
0

for this type of uri content://com.android.providers.media.documents/document/document%3A19298 or uri.getAuthority()is any of these

"com.google.android.apps.docs.storage".equals(uri.getAuthority()) || "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority());

use this function

private static String getDriveFilePath(Uri uri, Context context) {
        Uri returnUri = uri;
        Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);

        int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
        returnCursor.moveToFirst();
        String name = (returnCursor.getString(nameIndex));
        String size = (Long.toString(returnCursor.getLong(sizeIndex)));
        File file = new File(context.getCacheDir(), name);
        try {
            InputStream inputStream = context.getContentResolver().openInputStream(uri);
            FileOutputStream outputStream = new FileOutputStream(file);
            int read = 0;
            int maxBufferSize = 1 * 1024 * 1024;
            int bytesAvailable = inputStream.available();

            //int bufferSize = 1024;
            int bufferSize = Math.min(bytesAvailable, maxBufferSize);

            final byte[] buffers = new byte[bufferSize];
            while ((read = inputStream.read(buffers)) != -1) {
                outputStream.write(buffers, 0, read);
            }
            Log.e("File Size", "Size " + file.length());
            inputStream.close();
            outputStream.close();
            Log.e("File Path", "Path " + file.getPath());
            Log.e("File Size", "Size " + file.length());
        } catch (Exception e) {
            Log.e("Exception", e.getMessage());
        }
        return file.getPath();
    }
Manaker answered 19/6, 2021 at 12:3 Comment(0)
A
0

This answer latest API and latest android version. Date: 08/10/2023

You must declare in AndroidManifest.xml

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

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="32"
    android:required="true"
    tools:ignore="ScopedStorage" />

Require run time permission

 if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
    {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_STORAGE_REQUEST_CODE);
    }

Handle the permission event

  @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode)
    {
        case MY_STORAGE_REQUEST_CODE:
        {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
            {
                if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
                {
                    // Storage Permission granted
                    // TODO
                }
            }
            else
            {
                Toast.makeText(this, "Storage Permission Denied", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

Pick the file using Intent.ACTION_GET_CONTENT

Note : OnActivityResult method is deprecated

Alternative using ActivityResultLauncher

Pick file

    Intent intent = new Intent();
    intent.setType("video/*");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    startActivityIntent.launch(intent);

Handle the pick file event

ActivityResultLauncher<Intent> startActivityIntent = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                if (result.getResultCode() == Activity.RESULT_OK) {
                    Intent data = result.getData();
                    Uri uri = data.getData();
                    String realPath = RealPathUtil.getRealPath(context, uri);
                }
            }
        }
);

use RealPathUtil.class file

It helps previous old API and versions. it reduce error's in android different versions

Ansell answered 8/10, 2023 at 1:52 Comment(0)
W
-1

@paul burke's answer works fine for both camera and gallery pictures for API level 19 and above, but it doesn't work if your Android project's minimum SDK is set to below 19, and some answers referring above doesn't work for both gallery and camera. Well, I have modified @paul burke's code which works for API level below 19. Below is the code.

public static String getPath(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >=
                             Build.VERSION_CODES.KITKAT;
    Log.i("URI",uri+"");
    String result = uri+"";

    // DocumentProvider
    // if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
    if (isKitKat && (result.contains("media.documents"))) {

        String[] ary = result.split("/");
        int length = ary.length;
        String imgary = ary[length-1];
        final String[] dat = imgary.split("%3A");

        final String docId = dat[1];
        final String type = dat[0];

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

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

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

    return null;
}

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;
}
Whiny answered 20/1, 2016 at 14:48 Comment(2)
I receive java.lang.IllegalArgumentException: None of the requested columns can be provided when selecting a Google Doc imageBales
@dirkoneill I am getting the same Exception. Did you find a fix?Tsingyuan
C
-4

The answer to your question is that you need to have permissions. Type the following code in your manifest.xml file:

<uses-sdk  android:minSdkVersion="8"   android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_OWNER_DATA"></uses-permission>
<uses-permission android:name="android.permission.READ_OWNER_DATA"></uses-permission>`

It worked for me...

Confederation answered 5/2, 2014 at 4:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.