From 79a175ab56e03b4e8031e48c139060a0f434c0ab Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 15 May 2026 22:50:30 +0000 Subject: fix: handle application/octet-stream for .gpx on Android 16 (Google Files) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Google Files uses application/octet-stream for .gpx because Android 16's MimeTypeMap has no entry for that extension. The ExternalStorageProvider content URI encodes the full file path, so pathSuffix=".gpx" keeps the ACTION_VIEW filter targeted without matching all binary files. ACTION_SEND (share sheet) filters added for typed MIME variants; code now handles both actions — EXTRA_STREAM for SEND, intent.data for VIEW. For octet-stream and generic XML types, OpenableColumns.DISPLAY_NAME is checked before parsing (uri.lastPathSegment is an opaque ID for content URIs, not the filename). All decisions logged to NavLogger. https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G --- android-app/app/src/main/AndroidManifest.xml | 38 ++++++++++++++++++++-- .../src/main/kotlin/org/terst/nav/MainActivity.kt | 29 ++++++++++++----- 2 files changed, 56 insertions(+), 11 deletions(-) (limited to 'android-app/app/src/main') diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml index 196932d..a10d503 100644 --- a/android-app/app/src/main/AndroidManifest.xml +++ b/android-app/app/src/main/AndroidManifest.xml @@ -46,9 +46,7 @@ - + @@ -59,6 +57,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt index 07adb35..bfa285d 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt @@ -140,33 +140,46 @@ class MainActivity : AppCompatActivity(), SafetyFragment.SafetyListener { } private fun handleIncomingGpx(intent: Intent?) { - if (intent?.action != Intent.ACTION_VIEW) return - val uri = intent.data ?: return + val action = intent?.action ?: return + // Pull the URI from the right place depending on whether this is a direct open + // (ACTION_VIEW: URI in intent.data) or a share-sheet send (ACTION_SEND: URI in EXTRA_STREAM). + val uri = when (action) { + Intent.ACTION_VIEW -> intent.data + Intent.ACTION_SEND -> intent.getParcelableExtra(Intent.EXTRA_STREAM, android.net.Uri::class.java) + else -> null + } ?: return + val mime = intent.type ?: contentResolver.getType(uri) ?: "" when { - // Known GPX MIME types — proceed immediately. mime.contains("gpx", ignoreCase = true) -> Unit - // Generic XML MIME (text/xml, application/xml): the Android system MimeTypeMap - // often lacks application/gpx+xml for .gpx, so file managers fall back to an - // XML type. Verify the display name before parsing to avoid opening random XML. - mime.contains("xml", ignoreCase = true) -> { + // Generic XML or octet-stream: confirm it's actually a .gpx file by display name + // before parsing. For content URIs from Google Files, lastPathSegment is an opaque + // ID — OpenableColumns.DISPLAY_NAME is the reliable source of the actual filename. + mime.contains("xml", ignoreCase = true) || mime == "application/octet-stream" -> { val displayName = contentResolver.query( uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null )?.use { c -> if (c.moveToFirst()) c.getString(0) else null } - if (displayName?.lowercase()?.endsWith(".gpx") != true) return + if (displayName?.lowercase()?.endsWith(".gpx") != true) { + NavLogger.i("track", "handleIncomingGpx: skipping non-gpx file (mime=$mime displayName=$displayName)") + return + } } else -> return } + + NavLogger.i("track", "handleIncomingGpx: action=$action mime=$mime uri=$uri") lifecycleScope.launch { runCatching { val points = withContext(Dispatchers.IO) { contentResolver.openInputStream(uri)?.use { GpxParser.parse(it) } ?: emptyList() } + NavLogger.i("track", "handleIncomingGpx: parsed ${points.size} points") if (points.isNotEmpty()) { viewModel.addImportedTrack(points) showOverlay(SavedTracksFragment()) } }.onFailure { e -> + NavLogger.e("track", "handleIncomingGpx: failed: ${e.javaClass.simpleName}: ${e.message}") Log.e("MainActivity", "Failed to open GPX: ${e.message}") } } -- cgit v1.2.3