From 5eeb80fe962d8b907111b895422ddcf40f41665c Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Sat, 6 Jun 2026 01:03:29 +0300 Subject: [PATCH] Fix Android crash when sharing unreadable media into the app (#10674) --- .../ExpoReceiveAndroidIntentsModule.kt | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/modules/expo-receive-android-intents/android/src/main/java/xyz/blueskyweb/app/exporeceiveandroidintents/ExpoReceiveAndroidIntentsModule.kt b/modules/expo-receive-android-intents/android/src/main/java/xyz/blueskyweb/app/exporeceiveandroidintents/ExpoReceiveAndroidIntentsModule.kt index d7b9e0e468..71400672be 100644 --- a/modules/expo-receive-android-intents/android/src/main/java/xyz/blueskyweb/app/exporeceiveandroidintents/ExpoReceiveAndroidIntentsModule.kt +++ b/modules/expo-receive-android-intents/android/src/main/java/xyz/blueskyweb/app/exporeceiveandroidintents/ExpoReceiveAndroidIntentsModule.kt @@ -119,17 +119,15 @@ class ExpoReceiveAndroidIntentsModule : Module() { uris: List, text: String?, ) { - var allParams = "" + // Some URIs we receive may be unreadable (revoked permission, deleted file, + // a provider that rejects the read). Skip those rather than crashing the + // whole app, since this runs synchronously on the module init path. + val allParams = + uris + .mapNotNull { uri -> getImageInfo(uri) } + .joinToString(",") { info -> buildUriData(info) } - uris.forEachIndexed { index, uri -> - val info = getImageInfo(uri) - val params = buildUriData(info) - allParams = "${allParams}$params" - - if (index < uris.count() - 1) { - allParams = "$allParams," - } - } + if (allParams.isEmpty()) return val encodedUris = URLEncoder.encode(allParams, "UTF-8") val encodedText = text?.let { URLEncoder.encode(it, "UTF-8") } @@ -158,9 +156,16 @@ class ExpoReceiveAndroidIntentsModule : Module() { } val file = createFile(extension) - val out = FileOutputStream(file) - appContext.currentActivity?.contentResolver?.openInputStream(uri)?.use { - it.copyTo(out) + // The URI may be unreadable (revoked permission, deleted file, or a + // provider that rejects the read). Bail rather than crashing the whole + // app, since this runs synchronously on the module init path. + try { + FileOutputStream(file).use { out -> + val input = appContext.currentActivity?.contentResolver?.openInputStream(uri) ?: return + input.use { it.copyTo(out) } + } + } catch (e: Exception) { + return } val info = getVideoInfo(uri) ?: return @@ -176,8 +181,15 @@ class ExpoReceiveAndroidIntentsModule : Module() { } } - private fun getImageInfo(uri: Uri): Map { - val bitmap = MediaStore.Images.Media.getBitmap(appContext.currentActivity?.contentResolver, uri) + private fun getImageInfo(uri: Uri): Map? { + val bitmap = + try { + MediaStore.Images.Media.getBitmap(appContext.currentActivity?.contentResolver, uri) + } catch (e: Exception) { + // The URI may be unreadable (revoked permission, deleted file, or a + // provider that rejects the read). Skip this image rather than crash. + return null + } ?: return null // We have to save this so that we can access it later when uploading the image. // createTempFile will automatically place a unique string between "img" and "temp.jpeg" val file = createFile("jpeg") @@ -195,10 +207,18 @@ class ExpoReceiveAndroidIntentsModule : Module() { private fun getVideoInfo(uri: Uri): Map? { val retriever = MediaMetadataRetriever() - retriever.setDataSource(appContext.currentActivity, uri) - - val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() - val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() + val width: Int? + val height: Int? + try { + retriever.setDataSource(appContext.currentActivity, uri) + width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() + height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() + } catch (e: Exception) { + // The URI may be unreadable or not a valid media source. Skip rather than crash. + return null + } finally { + retriever.release() + } if (width == null || height == null) { return null