From f3731aa5f68f05eab48620be92db0b478721a654 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Mon, 11 Aug 2025 17:28:03 +0200 Subject: [PATCH] improve handling of file intents --- .../domain/services/sharing_intent.dart | 40 +++++++++++++--- .../SimpleIntentReceiverPlugin.kt | 47 ++++++++++++++----- .../pigeons/Intent.g.kt | 9 ++-- .../lib/src/pigeons/intent.g.dart | 9 +++- .../pigeons/intent.dart | 2 + 5 files changed, 82 insertions(+), 25 deletions(-) diff --git a/app/lib/features/share_intent/domain/services/sharing_intent.dart b/app/lib/features/share_intent/domain/services/sharing_intent.dart index 62b75123..d4813b0e 100644 --- a/app/lib/features/share_intent/domain/services/sharing_intent.dart +++ b/app/lib/features/share_intent/domain/services/sharing_intent.dart @@ -20,6 +20,8 @@ import 'dart:async'; import 'package:mime/mime.dart' as mime; +import 'package:nullability/nullability.dart'; +import 'package:path/path.dart' as p; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:simple_intent_receiver/simple_intent_receiver.dart'; @@ -44,13 +46,37 @@ final _sharingIntentTransformer = if (data != null) { if (uri_to_file.isUriSupported(data)) { - final file = await uri_to_file.toFile(data); - final mimeType = mime.lookupMimeType(file.path); - switch (mimeType) { - case 'application/pdf': - sink.add(ReceivedIntentParameter(data, null)); - default: - logger.w('Unhandled mime type: $mimeType'); + var path = data; + if (p.extension(data).whenNotEmpty == null) { + if (intent.mimeType.whenNotEmpty != null) { + final ext = mime.extensionFromMime(intent.mimeType!); + if (ext != null) { + path = p.setExtension(path, '.$ext'); + } else { + logger.w( + 'Could not determine file extension for: ${intent.mimeType}', + ); + } + } else { + logger.w( + 'Received intent without extension and mime type $path', + ); + } + } + + try { + final file = await uri_to_file.toFile(path); + final mimeType = mime.lookupMimeType(file.path); + switch (mimeType) { + case 'application/pdf': + sink.add(ReceivedIntentParameter(path, null)); + default: + logger.w('Unhandled mime type: $mimeType'); + } + } catch (e) { + logger.e('Failed to convert URI to file: $e'); + // Fallback: pass the original URI + sink.add(ReceivedIntentParameter(data, null)); } } else { sink.add(ReceivedIntentParameter(data, null)); diff --git a/packages/simple_intent_receiver/android/src/main/kotlin/eu/weblibre/simple_intent_receiver/SimpleIntentReceiverPlugin.kt b/packages/simple_intent_receiver/android/src/main/kotlin/eu/weblibre/simple_intent_receiver/SimpleIntentReceiverPlugin.kt index d5ef7473..5a06c07c 100644 --- a/packages/simple_intent_receiver/android/src/main/kotlin/eu/weblibre/simple_intent_receiver/SimpleIntentReceiverPlugin.kt +++ b/packages/simple_intent_receiver/android/src/main/kotlin/eu/weblibre/simple_intent_receiver/SimpleIntentReceiverPlugin.kt @@ -3,6 +3,7 @@ package eu.weblibre.simple_intent_receiver import android.app.Activity import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Bundle import io.flutter.Log import io.flutter.embedding.engine.plugins.FlutterPlugin @@ -30,10 +31,8 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N activity = binding.activity binding.addOnNewIntentListener(this) - // Process the initial intent if available binding.activity.intent?.let { intent -> - val uri = intent.toUri(0); - + val uri = intent.toUri(0) if (lastHandledIntent != uri) { handleIntent(intent) lastHandledIntent = uri @@ -55,15 +54,43 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N } override fun onNewIntent(intent: Intent): Boolean { - // Update the activity's intent to ensure proper state activity?.setIntent(intent) return handleIntent(intent) } private fun handleIntent(intent: Intent): Boolean { - // Check if this intent is coming from a different task + // Grant URI permissions for content URIs + intent.data?.let { uri -> + if (uri.scheme == "content") { + try { + activity?.grantUriPermission( + context.packageName, + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } catch (e: Exception) { + Log.w("SimpleIntentReceiver", "Could not grant URI permission for: $uri", e) + } + } + } + + // Handle SEND action with STREAM extra + intent.getStringExtra(Intent.EXTRA_STREAM)?.let { streamUri -> + try { + val uri = Uri.parse(streamUri) + if (uri.scheme == "content") { + activity?.grantUriPermission( + context.packageName, + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } + } catch (e: Exception) { + Log.w("SimpleIntentReceiver", "Could not grant URI permission for stream: $streamUri", e) + } + } + if (intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0) { - // Clear any existing tasks with this activity intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } @@ -77,25 +104,20 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N val data = intent.dataString val fromPackageName = intent.getPackage() - // Extract categories val categories = ArrayList() intent.categories?.let { categories.addAll(it) } - // Extract extras val extras = HashMap() intent.extras?.let { bundle -> for (key in bundle.keySet()) { try { when (val value = bundle.get(key)) { - // Handle Bundle objects specially is Bundle -> { - // Convert nested Bundle to Map val bundleMap = HashMap() for (bundleKey in value.keySet()) { val bundleValue = value.get(bundleKey) - // Only add primitive types and strings that can be safely serialized if (bundleValue == null || bundleValue is String || bundleValue is Boolean || bundleValue is Int || bundleValue is Long || bundleValue is Double || @@ -107,12 +129,10 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N } extras[key] = bundleMap } - // Handle other types that Flutter can serialize null, is String, is Boolean, is Int, is Long, is Double, is Float, is ByteArray, is IntArray, is LongArray, is DoubleArray, is FloatArray -> { extras[key] = value } - // For any other types, convert to string else -> { extras[key] = value.toString() } @@ -129,6 +149,7 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N action = action, data = data, categories = categories, + mimeType = intent.type, extra = extras ) } diff --git a/packages/simple_intent_receiver/android/src/main/kotlin/eu/weblibre/simple_intent_receiver/pigeons/Intent.g.kt b/packages/simple_intent_receiver/android/src/main/kotlin/eu/weblibre/simple_intent_receiver/pigeons/Intent.g.kt index 2ad3992b..78a3ae94 100644 --- a/packages/simple_intent_receiver/android/src/main/kotlin/eu/weblibre/simple_intent_receiver/pigeons/Intent.g.kt +++ b/packages/simple_intent_receiver/android/src/main/kotlin/eu/weblibre/simple_intent_receiver/pigeons/Intent.g.kt @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v25.5.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -67,6 +67,7 @@ data class Intent ( val action: String? = null, val data: String? = null, val categories: List, + val mimeType: String? = null, val extra: Map ) { @@ -76,8 +77,9 @@ data class Intent ( val action = pigeonVar_list[1] as String? val data = pigeonVar_list[2] as String? val categories = pigeonVar_list[3] as List - val extra = pigeonVar_list[4] as Map - return Intent(fromPackageName, action, data, categories, extra) + val mimeType = pigeonVar_list[4] as String? + val extra = pigeonVar_list[5] as Map + return Intent(fromPackageName, action, data, categories, mimeType, extra) } } fun toList(): List { @@ -86,6 +88,7 @@ data class Intent ( action, data, categories, + mimeType, extra, ) } diff --git a/packages/simple_intent_receiver/lib/src/pigeons/intent.g.dart b/packages/simple_intent_receiver/lib/src/pigeons/intent.g.dart index bda22e4e..835377f3 100644 --- a/packages/simple_intent_receiver/lib/src/pigeons/intent.g.dart +++ b/packages/simple_intent_receiver/lib/src/pigeons/intent.g.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v25.5.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -38,6 +38,7 @@ class Intent { this.action, this.data, required this.categories, + this.mimeType, required this.extra, }); @@ -49,6 +50,8 @@ class Intent { List categories; + String? mimeType; + Map extra; List _toList() { @@ -57,6 +60,7 @@ class Intent { action, data, categories, + mimeType, extra, ]; } @@ -71,7 +75,8 @@ class Intent { action: result[1] as String?, data: result[2] as String?, categories: (result[3] as List?)!.cast(), - extra: (result[4] as Map?)!.cast(), + mimeType: result[4] as String?, + extra: (result[5] as Map?)!.cast(), ); } diff --git a/packages/simple_intent_receiver/pigeons/intent.dart b/packages/simple_intent_receiver/pigeons/intent.dart index f517c251..d280733e 100644 --- a/packages/simple_intent_receiver/pigeons/intent.dart +++ b/packages/simple_intent_receiver/pigeons/intent.dart @@ -5,6 +5,7 @@ class Intent { final String? action; final String? data; final List categories; + final String? mimeType; final Map extra; Intent({ @@ -12,6 +13,7 @@ class Intent { required this.action, required this.data, required this.categories, + required this.mimeType, required this.extra, }); }