Skip to content

Commit 082fc72

Browse files
authored
fix(android): request ACCESS_MEDIA_LOCATION for GPS data on content URIs (#21)
On Android 10+, GPS EXIF data is redacted from content:// URIs. Auto-request ACCESS_MEDIA_LOCATION at runtime and use MediaStore.setRequireOriginal() to access unredacted location data. Closes #6
1 parent 9d3a1fc commit 082fc72

3 files changed

Lines changed: 49 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const tags = await Exify.read(uri)
3434
console.log(tags)
3535
```
3636

37+
> **Note:** On Android 10+, GPS data is redacted from `content://` URIs by default. The library automatically requests `ACCESS_MEDIA_LOCATION` at runtime to access unredacted location data. Your app must have media read access (`READ_MEDIA_IMAGES` or `READ_EXTERNAL_STORAGE`) granted first. If you're already using a library like [`expo-media-library`](https://docs.expo.dev/versions/latest/sdk/media-library/) that grants `ACCESS_MEDIA_LOCATION`, exify will use the existing grant.
38+
3739
### Writing Exif
3840

3941
```ts
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
23
</manifest>

android/src/main/java/com/lodev09/exify/ExifyModule.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
package com.lodev09.exify
22

3+
import android.Manifest
4+
import android.content.pm.PackageManager
35
import android.net.Uri
6+
import android.os.Build
7+
import android.provider.MediaStore
8+
import androidx.core.content.ContextCompat
49
import androidx.exifinterface.media.ExifInterface
510
import com.facebook.react.bridge.Arguments
611
import com.facebook.react.bridge.Promise
712
import com.facebook.react.bridge.ReactApplicationContext
813
import com.facebook.react.bridge.ReadableMap
914
import com.facebook.react.bridge.ReadableType
15+
import com.facebook.react.modules.core.PermissionAwareActivity
16+
import com.facebook.react.modules.core.PermissionListener
1017
import com.facebook.react.util.RNLog
1118
import com.lodev09.exify.ExifyUtils.formatTags
1219
import java.io.IOException
1320

1421
private const val ERROR_TAG = "E_EXIFY_ERROR"
22+
private const val MEDIA_LOCATION_REQUEST_CODE = 4209
1523

1624
class ExifyModule(
1725
reactContext: ReactApplicationContext,
@@ -31,10 +39,48 @@ class ExifyModule(
3139
return
3240
}
3341

42+
// On Android Q+, request ACCESS_MEDIA_LOCATION for unredacted GPS data
43+
if (scheme == "content" && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
44+
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_MEDIA_LOCATION) !=
45+
PackageManager.PERMISSION_GRANTED
46+
) {
47+
val activity = context.currentActivity as? PermissionAwareActivity
48+
if (activity != null) {
49+
activity.requestPermissions(
50+
arrayOf(Manifest.permission.ACCESS_MEDIA_LOCATION),
51+
MEDIA_LOCATION_REQUEST_CODE,
52+
PermissionListener { requestCode, _, _ ->
53+
if (requestCode == MEDIA_LOCATION_REQUEST_CODE) {
54+
readExif(uri, photoUri, scheme, promise)
55+
true
56+
} else {
57+
false
58+
}
59+
},
60+
)
61+
return
62+
}
63+
}
64+
65+
readExif(uri, photoUri, scheme, promise)
66+
}
67+
68+
private fun readExif(
69+
uri: String,
70+
photoUri: Uri,
71+
scheme: String,
72+
promise: Promise,
73+
) {
3474
try {
3575
val inputStream =
3676
if (scheme == "http" || scheme == "https") {
3777
java.net.URL(uri).openStream()
78+
} else if (scheme == "content" && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
79+
try {
80+
context.contentResolver.openInputStream(MediaStore.setRequireOriginal(photoUri))
81+
} catch (e: SecurityException) {
82+
context.contentResolver.openInputStream(photoUri)
83+
}
3884
} else {
3985
context.contentResolver.openInputStream(photoUri)
4086
}

0 commit comments

Comments
 (0)