Android Gallery on Android 4.4 (KitKat) returns different URI for Intent.ACTION_GET_CONTENT
Solution 1
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
Solution 2
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.
Solution 3
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.
Solution 4
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.
Solution 5
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.
Related videos on Youtube
Michael Greifeneder
Updated on June 20, 2021Comments
-
Michael Greifeneder about 3 years
Before KitKat (or before the new Gallery) the
Intent.ACTION_GET_CONTENT
returned a URI like thiscontent://media/external/images/media/3951.
Using the
ContentResolver
and quering forMediaStore.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?
-
CommonsWare over 10 yearsOff 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 viaContentResolver
. I have long been nervous about apps that assume that acontent://
Uri
that represents a file can always be converted into aFile
. -
Michael Greifeneder over 10 yearsI think a valid answer is to change to the
ContentResolver
and work withUri
instead of File-URLs. I will do that. It also enables better handling of non-GalleryUri
s. -
Snake over 10 years@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?
-
darrenp over 10 years@CommonsWare 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 theContentResolver
to a pre-designated place so it has a known filename. However, this sounds wasteful to me. Any other suggestions? -
CommonsWare over 10 years@darrenp: Ummm..., rewrite the native code to work with an
InputStream
over JNI? There aren't all that many options for you, unfortunately. -
darrenp over 10 yearsThat'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. -
Spry Techies almost 10 years@Exception there is a bug reported in cordova over this issue so try finder answer it is working
-
Exception almost 10 years@SpryTechies But that works only for images, what about other media types.
-
Spry Techies almost 10 years@Exception Which media you wants please specify
-
Exception almost 10 years@SpryTechies I mean to say video file.
-
DeltaCap019 about 9 years
-
surhidamatya over 8 yearsbetter answer is here stackoverflow.com/questions/20067508/… from Paul Burke
-
aslamhossin over 3 yearsMediaStore.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.
-
-
Michael Greifeneder over 10 yearsWould 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.
-
finder over 10 yearsMy old code refused to work on KitKat. I had to read the manual. Well, that new permissions are not required.
-
user65721 over 10 yearsit 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.
-
Álvaro over 10 yearsCould 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.
-
fattire over 10 yearsTo 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 newfile://
intent using the path you extracted, thenStartActivity()
and let your app pick it up. I know, terrible. -
Colin M. over 10 yearsBased 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.
-
Colin M. over 10 yearsThis 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.
-
Dan2552 over 10 yearsThis isn't working for me with a Nexus 4 running Kitkat but it is with a Galaxy S3 running 4.1.2
-
Josh over 10 yearsThis worked fantastically on a 4.4 Nexus 5 Documents UI and some other pref KitKat devices using the standard gallery apps, thanks Paul!
-
RuAware over 10 yearsThanks 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...
-
Michał Klimczak over 10 years@Dan2552 which part is not working? Do you get any exception?
-
Dan2552 over 10 yearsTurned 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.
-
Paul Burke over 10 years@RuAware When you select a Drive file, it gives back
Authority: com.google.android.apps.docs.storage
andSegments: [document, acc=1;doc=667]
. I'm not sure, but assume that thedoc
value is theUri
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. -
RuAware over 10 yearsThanks, 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 again
-
Snake over 10 years@voytez, Can this URI returned through your message converted to the full real path of the image?
-
voytez over 10 yearsI 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.
-
MBillau over 10 yearsHey, this so far has been working really well, thanks a lot! Did the Google Drive issue ever get sorted out?
-
nagoya0 over 10 yearsThis is not an answer to new Gallery app (strictly "Media Documents Provider" app) on KitKat.
-
nagoya0 over 10 yearsThe app that questioner calls "Gallery" is probably new file picker on kitkat. FYI: addictivetips.com/android/…
-
Russell Stewart over 10 yearsThis 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 allowed
-
Abou-Emish over 10 yearsi'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 :(
-
greaterKing over 10 yearspretty clever. i too needed the actual file uri
-
treejanitor over 10 yearsI 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. :-)
-
lorenzo-s about 10 yearsWorked for me too, instead of
Intent.ACTION_GET_CONTENT
. Anyway I kept theIntent.createChooser()
wrapper on the newIntent
, to let user choose the app for browsing, and worked as expected. Can someone see drawbacks for this solution? -
Dennis Anderson almost 10 yearsThis solution works wonderfull ! thanks for that. @RuAware i'm wondering if the Google Drive problem has been fixed! I needed myself too :)
-
j__m over 9 yearsthis 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 sources
-
Skywalker over 9 yearsif data.getData() is null, use: bitmap = (Bitmap)data.getExtras().get("data");
-
thomasforth over 9 yearsWhat a beautifully simple answer, thank you! For others following this answer 'cxt' refers to the current context and will usually be 'this'.
-
Michał Klimczak over 9 yearsThanks, I'll edit my answer so it's clear what cxt is
-
Quantum_VC about 9 yearsWhere are you displaying the image in second case?
-
saranya about 9 yearssorry 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 need
-
izac89 about 9 yearsBy using
if (isKitKat && DocumentsContract.isDocumentUri(context, uri))
are you assuming that ifisKitKat
is false, thanDocumentsContract.isDocumentUri(context, uri)
won't be checked (reasonable thinking) ? if so, then how can you be sure about that? if not, then shouldn't theif (isKitKat)
be checked first, thenif (DocumentsContract.isDocumentUri(context, uri))
? @Paul Burke -
weiy about 9 yearsCould you clarify if the ACTION_PICK / EXTERNAL_CONTENT_URI intent call works in pre-KitKat, such as 4.3 (API 18)?
-
mishkin about 9 yearsyou are my hero, this is exactly what I needed! works great for Google Drive files as well
-
rocknow about 9 yearsWorkaround for new Google Photos app gist.github.com/alexzaitsev/75c9b36dfcffd545c676
-
Philip Herbert about 9 yearsWhat file type is photoFile and mImgCropping?
-
Carlo Moretti almost 9 yearswhat if I need to pick an audio file?
-
Anand Savjani almost 9 years@PaulBurke : How to get pdf or text file from google drive using this code ?Please help me as soon as possible
-
Rafa over 8 yearsforget the dumpImageMetaData(uri); it is unnecessary
-
Alex Black over 8 yearsThis 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/532
-
Michał Klimczak over 8 yearsThis probably means that the file just isn't there. The URI seems OK.
-
Neerkoli over 8 yearsThink out of the box, right? :D This code works exactly like I was expecting.
-
Code over 8 yearsFor 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?
-
Yaroslav Mytkalyk over 8 years@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. -
Lumii over 8 yearsHi, 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 - stackoverflow.com/q/34465905/72437
-
YoungDinosaur over 8 yearsI receive java.lang.IllegalArgumentException: None of the requested columns can be provided when selecting a Google Doc image
-
Henry over 8 years@dirkoneill I am getting the same Exception. Did you find a fix?
-
jcromanu about 8 yearsMay 2016 and still working on marshmallow with a min target of api 15 .Great job
-
Shruti almost 8 yearsThank you so much, its working, you just made my day .
-
Sudhir Singh Khanger almost 8 years>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? -
Sudhir Singh Khanger almost 8 years>// TODO handle non-primary volumes. Does it work with external storage like external sd cards?
-
Sudhir Singh Khanger almost 8 years
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getActivity().getContentResolver(), uri)
. What is wrong with this? -
jacksonakj almost 8 yearsThank you. Using the Uri and ContentResolver this way simplified my application significantly when dealing with files.
-
Osify over 7 yearsI do similar and got IndexOutOfBound on Nexus 5X, Android 6 on this line:
cursor.getString(idx);
-
soshial over 7 yearsThe
_data
wouldn't work when ContentProvider doesn't support it. It is recommended to follow @CommonsWare instructions and not use full file path anymore, because it could be a file in Dropbox cloud instead of a real file. -
FaisalAhmed about 7 yearsi 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 do
-
Marian Klühspies about 7 yearsI had this in my app and now it breaks on Android 7+ nougat devices
-
Thracian over 6 yearsHow 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 argument
-
murt over 6 yearsI 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:958
-
snark over 6 yearsFor anyone who's wondering the quote comes from developer.android.com/guide/topics/providers/…
-
mikemike396 almost 6 yearsWorks perfect! Thank You
-
karanatwal.github.io over 5 yearsFor those who are getting
Error Unknown URI: content://downloads/public_downloads
stackoverflow.com/a/53021624/5996106 -
Bhavesh Moradiya over 5 yearsfilePath is getting null.. In uri: content://com.android.externalstorage.documents/document/799B-1419%3AScreenshot%2FScreenshot_20181117_162826.png
-
Harvi Sirja over 5 yearsI just want a working code of kotlin. It's work for me. thanks
-
Bartek Pacia over 4 yearspost the code for copyAndClose, the answer is not complete
-
Ravi Vaniya over 3 yearsPerfecto, Thx @Bringoff
-
jagadishlakkurcom jagadishlakk about 3 yearsPerefect Working
-
KJEjava48 over 2 years@RuAware did u find any way to pick file from drive and upload it to server??
-
KJEjava48 over 2 years@Paul Burke did u find any way to pick file from drive and upload it to server??
-
KJEjava48 over 2 years@Dennis Anderson did u find any way to pick file from drive and upload it to server??