androidandroid-jetpack-composeruntime-permissionsandroid-exifinterfacephoto-picker

Retrieving GPS EXIF data from images using AndroidX ExifInterface?


I'm targeting Android 13 and using the new photo picker to retrieve images. E.g.

val photoPicker = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.PickMultipleVisualMedia()
    ) { uris: List<Uri> -> handleUris(context, uris) ) }) }

photoPicker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))

fun handleUris(context: Context, uris: List<Uri>) {
    for (uri in uris) {
        context.contentResolver.openInputStream(uri)!!.use { stream ->
            ExifInterface(stream).let { exif ->
                val dateTime = exif.dateTime // present
                val latLng = exif.latLong // always null
            }
        }
    }
}

What i've found:

Since Android 10 GPS information is not included anymore in the EXIF data when you retrieve media. According to this video from 2019 you need to request the runtime permission ACCESS_MEDIA_LOCATION.

They explain that it is a runtime permission but the user cannot see the permission in the system settings (which by itself is quite bizarre). They also claim you need the READ_EXTERNAL_STORAGE alongside the media permission.

This piece of documentation talks about the media location permission as well.

Requesting the ACCESS_MEDIA_LOCATION permission using:

val locationPermissionState = rememberPermissionState(
        permission = ACCESS_MEDIA_LOCATION
)
locationPermissionState.launchPermissionRequest()

It actually does result in a permission dialog asking for "App to access photos and videos on this device". When granted the EXIF location data is still not present though.

Ah, right. According to the video we need the READ_EXTERNAL_STORAGE as well.

val locationPermissionState = rememberMultiplePermissionsState(
        permissions = listOf(ACCESS_MEDIA_LOCATION, READ_EXTERNAL_STORAGE)
)

No dialog is shown.. checking with ContextCompat.checkSelfPermission() it appears we have the ACCESS_MEDIA_LOCATION permission, but no READ_EXTERNAL_STORAGE. Why doesn't it show a dialog for it?

Looking at the docs for Android 13 it seems that READ_EXTERNAL_STORAGE is replaced with more granular permissions.

It appears when targeting Android 13 you should not use the READ_EXTERNAL_STORAGE permission anymore. For images we need READ_MEDIA_IMAGES instead. But requesting that permission does not show a dialog either.

I have also tried the MediaStore.setRequireOriginal(Uri) API which should prevent the EXIF metadata to be redacted, according to the docs this throws an UnsupportedOperationException when you don't have the ACCESS_MEDIA_LOCATION permission. But for me it throws an SecurityException with the message Calling uid does not have permission to access picker uri: content://etc.

So yeah, how do I get the GPS location in the EXIF data; what permissions do I actually need and how do I request them?

EDIT:

Ok, so on Android 11 when the photo picker is not available, it uses Intent(Intent.ACTION_OPEN_DOCUMENT) internally instead. The Uri that is returned from that looks like this: content://com.android.providers.media.documents/document/image%3A283

I am able to retrieve GPS position from that uri WITHOUT having granted ACCESS_MEDIA_LOCATION, READ_MEDIA_IMAGE or READ_EXTERNAL_STORAGE permissions. In contrary to most of the documentation I could find. Also no MediaStore.setRequireOriginal(Uri) is needed.

Which makes me think it is a photo picker issue instead of a permissions issue. So I figured I test on Android 13 without using the photo picker.

I'm using

val photoPicker = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.GetContent()
) { uri: Uri? ->  }

Instead now, which uses Intent(Intent.ACTION_GET_CONTENT) internally. The uri looks like this content://com.android.providers.media.documents/document/image%3A1000000049 and again I am able to pull GPS data without having any aforementioned permissions granted and no setRequireOriginal.

Which leaves me to conclude that the docs about media location are wrong.. the permission is not needed (unless they are talking about other metadata than latitude/longitude). And there must be something going on with the new photo picker uri's resulting in SecurityException when using setRequireOriginal and/or exif data redaction. Whether that is expected behaviour, I do not know yet.


Solution

  • As of now location metadata is redacted from the images retrieved via the new photo picker. It might get added in the future but for now we're stuck with the "old" Intent.ACTION_GET_CONTENT.

    Please star this issue.