From 6b291c469e6038a7100fbe3a7be7c32f6f2d02fe Mon Sep 17 00:00:00 2001
From: Peter Ombodi
Date: Mon, 27 Apr 2026 16:28:58 +0300
Subject: [PATCH] fix(mobile): resolve view intent MIME type with fallbacks
---
.../alextran/immich/media/MediaStoreUtils.kt | 45 +++++++++++++++++++
.../immich/viewintent/ViewIntentPlugin.kt | 2 +-
2 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/media/MediaStoreUtils.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/media/MediaStoreUtils.kt
index 3af520bb77..36f207bd44 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/media/MediaStoreUtils.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/media/MediaStoreUtils.kt
@@ -5,6 +5,10 @@ import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.provider.OpenableColumns
+import android.util.Log
+import android.webkit.MimeTypeMap
+
+private const val TAG = "MediaStoreUtils"
object MediaStoreUtils {
private fun externalFilesUri(): Uri =
@@ -31,6 +35,14 @@ object MediaStoreUtils {
else -> externalFilesUri()
}
+ fun resolveMimeType(context: Context, uri: Uri, fallbackMimeType: String? = null): String? {
+ return context.contentResolver.getType(uri)
+ ?: fallbackMimeType
+ ?: resolveMimeTypeFromDisplayName(context, uri)
+ ?: resolveMimeTypeFromPath(uri.path)
+ ?: resolveMimeTypeFromPath(uri.toString())
+ }
+
fun resolveLocalIdByRelativePath(context: Context, path: String, mimeType: String): String? {
val fileName = path.substringAfterLast('/', missingDelimiterValue = path)
val parent = path.substringBeforeLast('/', "").let { if (it.isEmpty()) "" else "$it/" }
@@ -76,6 +88,39 @@ object MediaStoreUtils {
)
}
+ private fun resolveMimeTypeFromDisplayName(context: Context, uri: Uri): String? {
+ return try {
+ context.contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
+ if (!cursor.moveToFirst()) {
+ return null
+ }
+
+ val displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ if (displayNameIndex < 0) {
+ return null
+ }
+
+ resolveMimeTypeFromPath(cursor.getString(displayNameIndex))
+ }
+ } catch (e: Exception) {
+ Log.w(TAG, "Failed to resolve MIME type from display name: $uri", e)
+ null
+ }
+ }
+
+ private fun resolveMimeTypeFromPath(path: String?): String? {
+ if (path.isNullOrBlank()) {
+ return null
+ }
+
+ val extension = path.substringAfterLast('.', missingDelimiterValue = "").substringBefore('?').substringBefore('#')
+ if (extension.isBlank()) {
+ return null
+ }
+
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.lowercase())
+ }
+
private fun queryLatestId(
context: Context,
tableUri: Uri,
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt
index 8f26aaa8c1..9773906682 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt
@@ -83,7 +83,7 @@ class ViewIntentPlugin : FlutterPlugin, ActivityAware, PluginRegistry.NewIntentL
ioScope.launch {
try {
- val mimeType = context.contentResolver.getType(uri)
+ val mimeType = MediaStoreUtils.resolveMimeType(context, uri, intent.type)
if (mimeType == null || (!mimeType.startsWith("image/") && !mimeType.startsWith("video/"))) {
callback(Result.success(null))
return@launch