4

Background

Targeting API 24 or above, instead of using a simple "Uri.fromFile" command, developers need to use FileProvider (or their own ContentProvider), in order to let other apps to access the app's files.

The problem

I try to open a camera app to let it save a file into my app's private storage, using this code:

    final Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    final File photoFile = FileHelper.generateTempCameraFile(this);
    mCameraCachedImageURI = FileProvider.getUriForFile(this, getPackageName()+ ".provider", photoFile);
    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCameraCachedImageURI);
    takePictureIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    startActivityForResult(takePictureIntent, REQ_CODE_PICK_IMAGE_FROM_CAMERA);

provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>

manifest file

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>

This starts fine, as the camera app is launched, but in the returned result, I fail to get the orientation of the image file using code that previously worked fine:

@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    int orientation = getOrientation(this,mCameraCachedImageURI );
    ...

public static int getOrientation(final Context context, final Uri photoUri) {
    Cursor cursor = null;
    try {
        cursor = context.getContentResolver().query(photoUri,
                new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);
    } catch (final Exception ignored) {
    }
    if (cursor == null) {
        // fallback
        return getOrientation(photoUri.getPath());
    }
    int result = 0;
    if (cursor.getCount() > 0) {
        cursor.moveToFirst();
        result = cursor.getInt(0);
    }
    cursor.close();
    return result;
}

public static int getOrientation(final String filePath) {
    if (TextUtils.isEmpty(filePath))
        return 0;
    try {
        final ExifInterface exifInterface = new ExifInterface(filePath);
        final int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                ExifInterface.ORIENTATION_NORMAL);
        int rotate = 0;
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_270:
                rotate = 270;
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                rotate = 180;
                break;
            case ExifInterface.ORIENTATION_ROTATE_90:
                rotate = 90;
                break;
        }
        return rotate;
    } catch (final IOException ignored) {
    }
    return 0;
}

This will crash, because the cursor doesn't have any columns while running this code.

Not only that, but even the fallback function (when changing the code to use it) doesn't work, as it adds an additional part to the path of the file ("external" in this case).

What I've found

It seems that the contentResolver uses the FileProvider here, instead of what was used on previous versions of Android.

Thing is, I need the ContentProvider of this URI in order to grant the camera app permission to access this file...

The question

How do I get the orientation using the above code (or even a better code), using the new FileProvider Uri ? Maybe I can force the ContentResolver to use the previously ContentProvider to get the needed column's data of the cursor?

Do I really have to create my own ContentProvider here?

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • why dont you use `Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)` or `Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)` ? more [here](https://developer.android.com/guide/topics/media/camera.html#saving-media) – pskink Dec 11 '16 at 15:14
  • I do not want to put public files, which are also available to the gallery app. This is a temporary file that should be used in the app itself, and will be deleted soon after. – android developer Dec 12 '16 at 12:19
  • if it is a private data only (no other apps see it), what do you need `FileProvider` for then? for `MediaStore.ACTION_IMAGE_CAPTURE` only? – pskink Dec 12 '16 at 13:28
  • @pskink It's private enough so that it shouldn't be for user to easily find and look at. I just had to take a camera image and then use it for something. – android developer Dec 13 '16 at 13:41

1 Answers1

0

A Uri is not a file. You are calling getPath() on a Uri and are treating it as if it were a file path. That only works if the scheme in the Uri is file. In your case, it is not.

You already have a File object pointing to the file. It is called photoFile. Hold onto that in a field of your activity, and save it in the saved instance state Bundle (as your process might get terminated while the camera app is in the foreground). Then, use photoFile. Don't use the built-in ExifInterface though, as it has security flaws.

How do I get the orientation using the above code (or even a better code), using the new FileProvider Uri ?

You don't. Use photoFile.

Maybe I can force the ContentResolver to use the previously ContentProvider to get the needed column's data of the cursor?

I have no idea why you would expect the getOrientation() that takes a Uri to work. You are querying the MediaStore for data about a Uri that does not come from the MediaStore.

Do I really have to create my own ContentProvider here?

No, because FileProvider is not your problem. You would have the same flawed getOrientation() methods with your own ContentProvider.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • If not using a uri or Exif, what should I use to get the orientation of the captured image file? I used ExifInterface only as a fallback, but now I have no idea what to use instead. – android developer Dec 11 '16 at 12:43
  • @androiddeveloper: "what should I use to get the orientation of the captured image file?" -- a random number generator. Seriously, if you're not going to look in the JPEG EXIF headers for the orientation, there is no way to know the orientation. The `MediaStore` examines the EXIF headers to find the orientation that it caches in its own database, for images that it has indexed. – CommonsWare Dec 11 '16 at 12:47
  • How do I get the orientation, I mean? What should I call? Which function? I have both the file path and the Uri that was created from FileProvider. Using the MediaStore on a uri didn't work, no matter if it's of the FileProvider or just of "Uri.fromFile" – android developer Dec 11 '16 at 12:52
  • @androiddeveloper: Call `getOrientation(final String filePath)`. It's in your code. However, the value for `filePath` is not `getPath()` on the `Uri`. It is `photoFile.getAbsolutePath()`. And, I recommend switching to a different `ExifInterface` implementation, as the one in the Android SDK [has security flaws](https://commonsware.com/blog/2016/09/08/dealing-exifinterface-security-flaw.html). – CommonsWare Dec 11 '16 at 12:55
  • But you just said not to use the ExifInterface API... What should I use instead? Is there any other API that Android has for this ? Also, you say that MediaStore itself uses the same API anyway? – android developer Dec 11 '16 at 12:57
  • @androiddeveloper: "But you just said not to use the ExifInterface API" -- no, I did not. I wrote: "I recommend switching to a different ExifInterface implementation, as the one in the Android SDK [has security flaws](https://commonsware.com/blog/2016/09/08/dealing-exifinterface-security-flaw.html)". Here, "[has security flaws](https://commonsware.com/blog/2016/09/08/dealing-exifinterface-security-flaw.html)" has what is known as a "hyperlink". It links to https://commonsware.com/blog/2016/09/08/dealing-exifinterface-security-flaw.html – CommonsWare Dec 11 '16 at 13:26
  • @androiddeveloper: In that blog post, I explain the flaw and point out alternatives (described in greater detail in this blog post: https://commonsware.com/blog/2016/05/31/tale-two-exifinterfaces.html). [Here is a sample project](https://github.com/commonsguy/cw-omnibus/tree/master/Camera/EXIFRotater) that uses one of the alternatives for working with EXIF orientation headers. – CommonsWare Dec 11 '16 at 13:27
  • @androiddeveloper: It uses one of the AOSP implementations. I do not recall which. – CommonsWare Dec 11 '16 at 13:54
  • OK , thanks again. In terms of correctness, the ExifInterface API should be fine, though, right? – android developer Dec 12 '16 at 12:20
  • It seems the new support library allows you to use a consistent, safe usage of ExifInterface API : https://developer.android.com/topic/libraries/support-library/features.html#exif , https://developer.android.com/topic/libraries/support-library/revisions.html . Does it seem good enough? – android developer Dec 14 '16 at 08:37
  • @androiddeveloper: At first glance, it looks good. It does not have ties to the JHEAD library that was the source of the bugs. They do not have the JavaDocs online, so I'm not certain how compatible this `ExifInterface` is with the SDK one. But, it is definitely worth exploring. Thanks for pointing this out! – CommonsWare Dec 14 '16 at 11:39
  • I see. Say, is the built-in ExifInterface API considered secure and ok-to-use starting from some Android version, or is it exactly the same even on Android 7.1 (API 25) ? – android developer Dec 14 '16 at 12:35
  • 1
    @androiddeveloper: Android 7.0+ devices should be patched. Google offered a patch for 6.0 (and possibly 5.x -- I forget). However, it is unlikely that many 5.x/6.x devices will ever see that patch, due to the poor track record of OS upgrades. If your `minSdkVersion` is 24 or higher, using the SDK's `ExifInterface` should be fine; I'd use something else otherwise. – CommonsWare Dec 14 '16 at 12:41
  • Ok thank you. Please write about it and the support library in the answer. Maybe more visible this way... – android developer Dec 14 '16 at 22:19