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 a filePath
. That itself is annoying, as it is discouraged now to rely on paths - you should rather use Uri
s and ContentResolver
. OK.
Our Uri
named uri
can be retrieved from the intent in onActivityResult
(if you pick the picture from gallery with ACTION_GET_CONTENT
) or can be an Uri
that we previously had (if you pick the picture from camera and call intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
).
Our uri
can have two different schemas:
file://
schema. Those are pretty easy to treat, because they hold the path. You can call new ExifInterface(uri.getPath())
and you are done.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 with Context.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; }
My issues arise from Kitkat onward with content://
URIs. Kitkat introduces the Storage Access Framework
(see here) along with a new intent, ACTION_OPEN_DOCUMENT
, and a platform picker. However, it is said that
On 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 like image:2839
, but obviously breaks when wholeId
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.
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 have ContentResolver.openFileDescriptor()
and similar stuff, but no APIs to read metadata (which truly is in that file). There might be external libraries that read Exif
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.
You can call new ExifInterface(uri. getPath()) and you are done. Uris coming from gallery or other content providers usually have a content:// interface.
android.media.ExifInterface. This is a class for reading and writing Exif tags in various image file formats. Supported for reading: JPEG, PNG, WebP, HEIF, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF, AVIF. Supported for writing: JPEG, PNG, WebP, DNG.
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).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With