48

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 Uris 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)).

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

API19+

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.

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 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.

natario
  • 24,954
  • 17
  • 88
  • 158
  • Can you achieve what you're after via my [metadata-extractor](https://github.com/drewnoakes/metadata-extractor) library? So long as you can open a stream, it can process the file. – Drew Noakes Jan 09 '16 at 22:24
  • 13
    Android is a pile of technical debt and bugs – GaRRaPeTa Nov 03 '16 at 14:11
  • 2
    The ExifInterface class has a constructor taking an Inputstream from sdk level 24 https://developer.android.com/reference/android/media/ExifInterface.html#ExifInterface(java.io.InputStream) – Torleif Mar 09 '17 at 10:00
  • 9
    @Torleif nice to know, it will be useful in 2020. :-) – natario Mar 09 '17 at 10:26
  • 2
    @natario its not 2020 yet but there is a support library now. See my answer for example code. – startoftext Mar 27 '17 at 21:08
  • The only way I could read the exif orientation tag from a file, when the platform is <24 It was using https://github.com/drewnoakes/metadata-extractor ExifInterface was not returning any orientation tag for devices <24 – egonzal May 20 '19 at 14:42

7 Answers7

61

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).

startoftext
  • 3,846
  • 7
  • 40
  • 49
  • You are a true MVP. +1 – Sharp Edge Mar 27 '17 at 01:49
  • this is a perfect answer – code4j Jan 03 '18 at 06:15
  • 1
    Perfect! 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 – user1209216 Jan 18 '18 at 09:56
  • Still getting null values for `Uri`s that are not coming from the `MediaStore`. 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? – Alexandre Nussbaumer May 05 '19 at 14:15
  • 1
    I accidentally voted up, but orientation here always return 0 for me. So this was not useful. I posted my own answer below. – coolcool1994 Jul 27 '20 at 19:28
  • This is not supposed to be used on uris that reference images on the cloud. See [docs](https://developer.android.com/reference/android/media/ExifInterface#ExifInterface(java.io.InputStream)). – headsvk Jul 27 '22 at 13:16
  • Thank You! If this works in all scenarios - it is great solution. Works for me till now. – Andrij Dec 30 '22 at 16:52
17

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

alex.dorokhov
  • 1,218
  • 12
  • 17
12

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.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Convincing 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 Jan 09 '16 at 18:12
  • Will 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. – natario Jan 09 '16 at 18:15
  • @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 the `TAG_` 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 Jan 09 '16 at 18:35
  • @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` and `content` `Uri` values. If you get an `http`/`https` one, you'll need to use your favorite HTTP stack for that. For `ACTION_GET_CONTENT`, you should only get `file` or `content` `Uri` values back; for `ACTION_OPEN_DOCUMENT`, anything other than `content` is a bug in the storage provider AFAIK. – CommonsWare Jan 09 '16 at 18:36
  • Mark: I took your advice but went one step further and created a library project from the code. https://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'` – Greg Sep 14 '16 at 02:16
  • by using the SupportLibrary's ExifInterface, you can use a InputStream in the interface's constructor. 'compile 'com.android.support:exifinterface:26.1.0' – Vitor Hugo Schwaab Oct 18 '17 at 21:34
  • thanks for update 2020-01-10. AndroidX libraries (androidx.exifinterface.media) works fine. – İlker Elçora Mar 31 '20 at 11:34
  • @CommonsWare when consuming `ACTION_SEND` from google photos GPS data is missing when using ExifInterface with URI. any pointers? – Rahul Tiwari Aug 28 '20 at 16:46
  • @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. – CommonsWare Aug 28 '20 at 16:50
  • @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. – Rahul Tiwari Aug 28 '20 at 17:06
  • @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. – CommonsWare Aug 28 '20 at 17:16
  • Getting undefined orientation when I use an inputStream (obtained via a Uri returned as an ActivityContractResult) to get an ExifInterface – rm8x Mar 01 '21 at 18:05
  • 1
    @rm8x: There is no requirement for every image to have any particular EXIF tag. – CommonsWare Mar 01 '21 at 20:05
  • @CommonsWare on a xiaomi redmi using an inputStream as described above had an undefined orientation, but using https://github.com/drewnoakes/metadata-extractor was able to read the data. – rm8x Mar 02 '21 at 20:20
  • 1
    @rm8x: That might be a bug in the Jetpack `ExifInterface` implementation, then. If you can create a project with an image that fails with `ExifInterface` but succeeds with the other library, file a bug report! – CommonsWare Mar 02 '21 at 20:34
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!
}
coolcool1994
  • 3,704
  • 4
  • 39
  • 43
0

Android 10 API 30

Get Exif data from image URI

 public static Bitmap decodeBitmap( Context context, Uri imagePath) {
    Logger.d("decodeBitmap imagePath: " + imagePath.getPath());

    if (imagePath == null) {
        return null;
    }

    InputStream in;
    ExifInterface exif;
    Bitmap image = null;
    try {
        in = context.getContentResolver().openInputStream(imagePath);
        image = BitmapFactory.decodeStream(in);

        //Close input stream consumed for Bitmap decode
        in.close();

        // Open stream again for reading exif information for acquiring orientation details.
        // Use new input stream otherwise bitmap decode stream gets reset.
        in =  context.getContentResolver().openInputStream(imagePath);

        int orientation = ExifInterface.ORIENTATION_UNDEFINED;
        try {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
                exif = new ExifInterface(in);
            }else{
                exif = new ExifInterface(imagePath.getPath());
            }
            orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);

        } catch (IOException e) {
            Logger.d("IOException: " + e.getMessage());
        }

        //if you need, can correct orientation issues for gallery pick camera images with following.
        Logger.d("decodeBitmap orientation: " + orientation);
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
            case ExifInterface.ORIENTATION_TRANSPOSE:
                image = rotateImage(image, ROTATE_90);
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
            case ExifInterface.ORIENTATION_FLIP_VERTICAL:
                image = rotateImage(image, ROTATE_180);
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
            case ExifInterface.ORIENTATION_TRANSVERSE:
                image = rotateImage(image, ROTATE_270);
                break;
            default:
                break;
        }
        in.close();
    }  catch (IOException e) {
        Logger.d("IOException", e.getMessage());
    }
     return image;
}
UdayaLakmal
  • 4,035
  • 4
  • 29
  • 40
0

Dependency for AndroidX

import androidx.exifinterface.media.ExifInterface

To create a new instance, simply use the constructor :

ExifInterface(...)

If you can't find the dependency, add the latest version to your gradle file (module: app)

dependencies {
    implementation "androidx.exifinterface:exifinterface:X.X.X"
}
GerbenDev
  • 26
  • 3
-1

In my case I have issues with getting orientation with InputStream. So instead of getting ExifInterface from InputStream I used FileDescriptor.

This solution was not working:

val inputStream = contentResolver.openInputStream(uri)
val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream)
val exifInterface = inputStream?.let { ExifInterface(inputStream) }
inputStream?.close()

I had better results when I opened separate InputStream for ExifInterface (but I did not like it that way):

val inputStream = contentResolver.openInputStream(uri)
val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream)
inputStream?.close()

val inputStream2 = contentResolver.openInputStream(uri)
val fileDescriptor = contentResolver.openFileDescriptor(uri, "r")?.fileDescriptor
val exifInterface = inputStream2?.let { ExifInterface(inputStream2) }
inputStream2?.close()

But I ended up with this method using FileDescriptor for construction of ExifInterface:

fun Context.getImageFromGallery(uri: Uri): Bitmap? {
    return try {
        val inputStream = contentResolver.openInputStream(uri)
        val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream)
        inputStream?.close()

        val fileDescriptor = contentResolver.openFileDescriptor(uri, "r")?.fileDescriptor
        val exifInterface = fileDescriptor?.let { ExifInterface(fileDescriptor) }

        return when (exifInterface?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
            ExifInterface.ORIENTATION_ROTATE_90 -> TransformationUtils.rotateImage(bitmap!!, 90)
            ExifInterface.ORIENTATION_ROTATE_180 -> TransformationUtils.rotateImage(bitmap!!, 180)
            ExifInterface.ORIENTATION_ROTATE_270 -> TransformationUtils.rotateImage(bitmap!!, 270)
            ExifInterface.ORIENTATION_NORMAL -> bitmap
            else -> bitmap
        }
    } catch (e: java.lang.Exception) {
        e.printStackTrace()
        null
    }
}