A final answer on how to get Exif data from URI
Solution 1
To expand on alex.dorokhov's answer with some sample code. The support library is a great way to go.
build.gradle
dependencies {
...
compile "com.android.support:exifinterface:25.0.1"
...
}
Example code:
import android.support.media.ExifInterface;
...
try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
ExifInterface exif = new ExifInterface(inputStream);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
} catch (IOException e) {
e.printStackTrace();
}
The reason I had to do it this way once we started targeting api 25 (maybe a problem on 24+ also) but still supporting back to api 19, on android 7 our app would crash if I passed in a URI to the camera that was just referencing a file. Hence I had to create a URI to pass to the camera intent like this.
FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider", tempFile);
The issue there is that file its not possible to turn the URI into a real file path (other than holding on to the temp file path).
Solution 2
Getting EXIF from a content URI (an InputStream actually) is now available in the support library. See: https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html
Solution 3
The following works with all apps I have tested, in any case:
That will only work if the Uri
happens to be something coming from the MediaStore
. It will fail if the Uri
happens to come from anything else.
The immediate answer is You don’t, you don’t find file paths from content uris in newer version of the OS. It could be said that not all content uris point to pictures or even files.
Correct. I have pointed this out on many occasions, such as here.
How are we supposed to use the ExifInterface class if we should not use paths?
You don't. Use other code to get the EXIF headers.
There might be external libraries that read Exif stuff from a stream, but I’m wondering about the common/platform way to solve this.
Use external libraries.
I have searched for similar code in google’s open source apps, but found nothing.
You will find some in the Mms app.
UPDATE: 2020-01-10: Use ExifInterface
from the AndroidX libraries. It supports using InputStream
to read in the EXIF data, and you can get an InputStream
for content identified by a Uri
by means of a ContentResolver
.
Solution 4
Don't use EXIF. You can get orientation of image from Uri like this:
private static int getOrientation(Context context, Uri photoUri) {
Cursor cursor = context.getContentResolver().query(photoUri,
new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);
if (cursor.getCount() != 1) {
cursor.close();
return -1;
}
cursor.moveToFirst();
int orientation = cursor.getInt(0);
cursor.close();
cursor = null;
//orientation here can be 90, 180, 270!
}
Related videos on Youtube
natario
Updated on June 05, 2022Comments
-
natario about 2 years
This topic has been discussed in lots of questions here, with mostly different results and, due to API changes and different types of URIs, no definitive answer.
I don’t have an answer myself, but let’s talk about it. The
ExifInterface
has a single constructor that accepts afilePath
. That itself is annoying, as it is discouraged now to rely on paths - you should rather useUri
s andContentResolver
. OK.Our
Uri
nameduri
can be retrieved from the intent inonActivityResult
(if you pick the picture from gallery withACTION_GET_CONTENT
) or can be anUri
that we previously had (if you pick the picture from camera and callintent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
).API<19
Our
uri
can have two different schemas:- Uris coming from cameras will mostly have a
file://
schema. Those are pretty easy to treat, because they hold the path. You can callnew ExifInterface(uri.getPath())
and you are done. - Uris coming from gallery or other content providers usually have a
content://
interface. I personally don’t know what that is about, but is driving me mad.
This second case, as far as I understand, should be treated with a
ContentResolver
that you can get withContext.getContentResolver()
. The following works with all apps I have tested, in any case:public static ExifInterface getPictureData(Context context, Uri uri) { String[] uriParts = uri.toString().split(":"); String path = null; if (uriParts[0].equals("content")) { // we can use ContentResolver. // let’s query the DATA column which holds the path String col = MediaStore.Images.ImageColumns.DATA; Cursor c = context.getContentResolver().query(uri, new String[]{col}, null, null, null); if (c != null && c.moveToFirst()) { path = c.getString(c.getColumnIndex(col)); c.close(); return new ExifInterface(path); } } else if (uriParts[0].equals("file")) { // it's easy to get the path path = uri.getEncodedPath(); return new ExifInterface(path); } return null; }
API19+
My issues arise from Kitkat onward with
content://
URIs. Kitkat introduces theStorage Access Framework
(see here) along with a new intent,ACTION_OPEN_DOCUMENT
, and a platform picker. However, it is said thatOn Android 4.4 and higher, you have the additional option of using the ACTION_OPEN_DOCUMENT intent, which displays a picker UI controlled by the system that allows the user to browse all files that other apps have made available. From this single UI, the user can pick a file from any of the supported apps.
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.
So to keeps this very simple, let’s say that we are ok with the old
ACTION_GET_CONTENT
: it will fire a chooser dialog where you can choose a gallery app.However, the content approach doesn’t work anymore. Sometimes it works on Kitkat, but never works on Lollipop, for example. I don’t know what exactly has changed.
I have searched and tried a lot; another approach taken for Kitkat specifically is:
String wholeId = DocumentsContract.getDocumentId(uri); String[] parts = wholeId.split(“:”); String numberId = parts[1]; Cursor c = context.getContentResolver().query( // why external and not internal ? MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{ col }, MediaStore.Images.Media._ID + “=?”, new String[]{ numberId }, null);
This works sometimes, but others not. Specifically, it works when
wholeId
is something likeimage:2839
, but obviously breaks whenwholeId
is simply a number.You can try this using the system picker (i.e. firing the gallery with
ACTION_OPEN_DOCUMENT
): if you choose an image from “Recents”, it works; if you choose an image from “Downloads”, it breaks.So how to?!
The immediate answer is You don’t, you don’t find file paths from content uris in newer version of the OS. It could be said that not all content uris point to pictures or even files.
That’s totally OK for me, and at first I worked to avoid this. But then, How are we supposed to use the ExifInterface class if we should not use paths?
I don’t understand how modern apps do this - finding orientation and metadata is an issue you immediately face, and
ContentResolver
does not offer any API in that sense. You haveContentResolver.openFileDescriptor()
and similar stuff, but no APIs to read metadata (which truly is in that file). There might be external libraries that readExif
stuff from a stream, but I’m wondering about the common/platform way to solve this.I have searched for similar code in google’s open source apps, but found nothing.
-
Drew Noakes over 8 yearsCan you achieve what you're after via my metadata-extractor library? So long as you can open a stream, it can process the file.
-
GaRRaPeTa over 7 yearsAndroid is a pile of technical debt and bugs
-
Saad over 7 yearsThe ExifInterface class has a constructor taking an Inputstream from sdk level 24 developer.android.com/reference/android/media/…
-
natario over 7 years@Torleif nice to know, it will be useful in 2020. :-)
-
startoftext over 7 years@natario its not 2020 yet but there is a support library now. See my answer for example code.
-
egonzal about 5 yearsThe only way I could read the exif orientation tag from a file, when the platform is <24 It was using github.com/drewnoakes/metadata-extractor ExifInterface was not returning any orientation tag for devices <24
- Uris coming from cameras will mostly have a
-
natario over 8 yearsConvincing and fast answer, thank you. I have seen you answering similar stuff and will do as you suggest. Anyway I must say that, if we were to judge by SO question and answers, most developers/apps out there are still trying to get paths! The vast majority of answers here still suggest (failing, thus my question from today) to query for paths, rather than open for streams. I believe something is missing from google’s side, either in docs or in deprecating ExifInterface. Just saying..
-
natario over 8 yearsWill ContentResolver.open(...) work for any URI/API level out there, or is there something else I should consider? I’m talking about URIs you request with a
image/*
type. -
CommonsWare over 8 years@mvai: "most developers/apps out there are still trying to get paths!" -- agreed. I have commented on many Stack Overflow questions about it. "I believe something is missing from google’s side, either in docs or in deprecating ExifInterface" -- docs certainly could use more love.
ExifInterface
was one of those classes they tossed in fairly early (2009) and haven't done much with other than update theTAG_
constants. On the whole, Google has been steering away from providing stuff that is not particularly Android-centric, in favor of letting third parties handle it. -
CommonsWare over 8 years@mvai: "Will ContentResolver.open(...) work for any URI/API level out there, or is there something else I should consider?" -- it will work for
file
andcontent
Uri
values. If you get anhttp
/https
one, you'll need to use your favorite HTTP stack for that. ForACTION_GET_CONTENT
, you should only getfile
orcontent
Uri
values back; forACTION_OPEN_DOCUMENT
, anything other thancontent
is a bug in the storage provider AFAIK. -
Greg almost 8 yearsMark: I took your advice but went one step further and created a library project from the code. github.com/dotloop/aosp-exif If you're using jitpack.io, you can just add this dependency:
compile 'com.github.dotloop:aosp-exif:1.0.0'
-
Sharp Edge over 7 yearsYou are a true MVP. +1
-
Vitor Hugo Schwaab over 6 yearsby using the SupportLibrary's ExifInterface, you can use a InputStream in the interface's constructor. 'compile 'com.android.support:exifinterface:26.1.0'
-
code4j over 6 yearsthis is a perfect answer
-
user1209216 over 6 yearsPerfect! I was really stuck how to correctly manage it on sdk <24, since any trying to extract real file path from uri is generally wrong. Thanks
-
Alexandre Nussbaumer about 5 yearsStill getting null values for
Uri
s that are not coming from theMediaStore
. Tested it with an image coming from the Google Photos cloud which is not saved on the phone using the support library. Has anybody found a solution? -
İlker Elçora about 4 yearsthanks for update 2020-01-10. AndroidX libraries (androidx.exifinterface.media) works fine.
-
coolcool1994 almost 4 yearsI accidentally voted up, but orientation here always return 0 for me. So this was not useful. I posted my own answer below.
-
Rahul Tiwari almost 4 years@CommonsWare when consuming
ACTION_SEND
from google photos GPS data is missing when using ExifInterface with URI. any pointers? -
CommonsWare almost 4 years@RahulTiwari: On Android 10+, you will encounter problems with getting EXIF data from images, for privacy reasons. And it is possible that Google Photos (or other apps) will redact EXIF headers on their own.
-
Rahul Tiwari almost 4 years@CommonsWare so there is no reliable way to get location data from
ACTION_SEND
content uri? mapping image/video to location is the primary use case for my app. -
CommonsWare almost 4 years@RahulTiwari: "so there is no reliable way to get location data from ACTION_SEND content uri?" -- there never really was, insofar as there was no requirement for the content being sent to you having location EXIF headers. It's just that it is somewhat less likely to get those headers.
-
rm8x over 3 yearsthis requires api 29
-
rm8x over 3 yearsGetting undefined orientation when I use an inputStream (obtained via a Uri returned as an ActivityContractResult) to get an ExifInterface
-
CommonsWare over 3 years@rm8x: There is no requirement for every image to have any particular EXIF tag.
-
rm8x over 3 years@CommonsWare on a xiaomi redmi using an inputStream as described above had an undefined orientation, but using github.com/drewnoakes/metadata-extractor was able to read the data.
-
CommonsWare over 3 years@rm8x: That might be a bug in the Jetpack
ExifInterface
implementation, then. If you can create a project with an image that fails withExifInterface
but succeeds with the other library, file a bug report! -
kivmii about 3 yearsDoes not require API 29. Works great while ExifInterface failed.