improve handling of file intents

This commit is contained in:
Fabian Freund
2025-08-11 17:28:03 +02:00
parent 33700bf509
commit f3731aa5f6
5 changed files with 82 additions and 25 deletions
@@ -20,6 +20,8 @@
import 'dart:async'; import 'dart:async';
import 'package:mime/mime.dart' as mime; 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/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:simple_intent_receiver/simple_intent_receiver.dart'; import 'package:simple_intent_receiver/simple_intent_receiver.dart';
@@ -44,13 +46,37 @@ final _sharingIntentTransformer =
if (data != null) { if (data != null) {
if (uri_to_file.isUriSupported(data)) { if (uri_to_file.isUriSupported(data)) {
final file = await uri_to_file.toFile(data); var path = data;
final mimeType = mime.lookupMimeType(file.path); if (p.extension(data).whenNotEmpty == null) {
switch (mimeType) { if (intent.mimeType.whenNotEmpty != null) {
case 'application/pdf': final ext = mime.extensionFromMime(intent.mimeType!);
sink.add(ReceivedIntentParameter(data, null)); if (ext != null) {
default: path = p.setExtension(path, '.$ext');
logger.w('Unhandled mime type: $mimeType'); } 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 { } else {
sink.add(ReceivedIntentParameter(data, null)); sink.add(ReceivedIntentParameter(data, null));
@@ -3,6 +3,7 @@ package eu.weblibre.simple_intent_receiver
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import io.flutter.Log import io.flutter.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
@@ -30,10 +31,8 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N
activity = binding.activity activity = binding.activity
binding.addOnNewIntentListener(this) binding.addOnNewIntentListener(this)
// Process the initial intent if available
binding.activity.intent?.let { intent -> binding.activity.intent?.let { intent ->
val uri = intent.toUri(0); val uri = intent.toUri(0)
if (lastHandledIntent != uri) { if (lastHandledIntent != uri) {
handleIntent(intent) handleIntent(intent)
lastHandledIntent = uri lastHandledIntent = uri
@@ -55,15 +54,43 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N
} }
override fun onNewIntent(intent: Intent): Boolean { override fun onNewIntent(intent: Intent): Boolean {
// Update the activity's intent to ensure proper state
activity?.setIntent(intent) activity?.setIntent(intent)
return handleIntent(intent) return handleIntent(intent)
} }
private fun handleIntent(intent: Intent): Boolean { 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) { if (intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0) {
// Clear any existing tasks with this activity
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
} }
@@ -77,25 +104,20 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N
val data = intent.dataString val data = intent.dataString
val fromPackageName = intent.getPackage() val fromPackageName = intent.getPackage()
// Extract categories
val categories = ArrayList<String>() val categories = ArrayList<String>()
intent.categories?.let { intent.categories?.let {
categories.addAll(it) categories.addAll(it)
} }
// Extract extras
val extras = HashMap<String, Any?>() val extras = HashMap<String, Any?>()
intent.extras?.let { bundle -> intent.extras?.let { bundle ->
for (key in bundle.keySet()) { for (key in bundle.keySet()) {
try { try {
when (val value = bundle.get(key)) { when (val value = bundle.get(key)) {
// Handle Bundle objects specially
is Bundle -> { is Bundle -> {
// Convert nested Bundle to Map
val bundleMap = HashMap<String, Any?>() val bundleMap = HashMap<String, Any?>()
for (bundleKey in value.keySet()) { for (bundleKey in value.keySet()) {
val bundleValue = value.get(bundleKey) val bundleValue = value.get(bundleKey)
// Only add primitive types and strings that can be safely serialized
if (bundleValue == null || bundleValue is String || if (bundleValue == null || bundleValue is String ||
bundleValue is Boolean || bundleValue is Int || bundleValue is Boolean || bundleValue is Int ||
bundleValue is Long || bundleValue is Double || bundleValue is Long || bundleValue is Double ||
@@ -107,12 +129,10 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N
} }
extras[key] = bundleMap extras[key] = bundleMap
} }
// Handle other types that Flutter can serialize
null, is String, is Boolean, is Int, is Long, is Double, is Float, null, is String, is Boolean, is Int, is Long, is Double, is Float,
is ByteArray, is IntArray, is LongArray, is DoubleArray, is FloatArray -> { is ByteArray, is IntArray, is LongArray, is DoubleArray, is FloatArray -> {
extras[key] = value extras[key] = value
} }
// For any other types, convert to string
else -> { else -> {
extras[key] = value.toString() extras[key] = value.toString()
} }
@@ -129,6 +149,7 @@ class SimpleIntentReceiverPlugin: FlutterPlugin, ActivityAware, PluginRegistry.N
action = action, action = action,
data = data, data = data,
categories = categories, categories = categories,
mimeType = intent.type,
extra = extras extra = extras
) )
} }
@@ -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 // See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
@@ -67,6 +67,7 @@ data class Intent (
val action: String? = null, val action: String? = null,
val data: String? = null, val data: String? = null,
val categories: List<String>, val categories: List<String>,
val mimeType: String? = null,
val extra: Map<String, Any?> val extra: Map<String, Any?>
) )
{ {
@@ -76,8 +77,9 @@ data class Intent (
val action = pigeonVar_list[1] as String? val action = pigeonVar_list[1] as String?
val data = pigeonVar_list[2] as String? val data = pigeonVar_list[2] as String?
val categories = pigeonVar_list[3] as List<String> val categories = pigeonVar_list[3] as List<String>
val extra = pigeonVar_list[4] as Map<String, Any?> val mimeType = pigeonVar_list[4] as String?
return Intent(fromPackageName, action, data, categories, extra) val extra = pigeonVar_list[5] as Map<String, Any?>
return Intent(fromPackageName, action, data, categories, mimeType, extra)
} }
} }
fun toList(): List<Any?> { fun toList(): List<Any?> {
@@ -86,6 +88,7 @@ data class Intent (
action, action,
data, data,
categories, categories,
mimeType,
extra, extra,
) )
} }
@@ -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 // 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 // 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.action,
this.data, this.data,
required this.categories, required this.categories,
this.mimeType,
required this.extra, required this.extra,
}); });
@@ -49,6 +50,8 @@ class Intent {
List<String> categories; List<String> categories;
String? mimeType;
Map<String, Object?> extra; Map<String, Object?> extra;
List<Object?> _toList() { List<Object?> _toList() {
@@ -57,6 +60,7 @@ class Intent {
action, action,
data, data,
categories, categories,
mimeType,
extra, extra,
]; ];
} }
@@ -71,7 +75,8 @@ class Intent {
action: result[1] as String?, action: result[1] as String?,
data: result[2] as String?, data: result[2] as String?,
categories: (result[3] as List<Object?>?)!.cast<String>(), categories: (result[3] as List<Object?>?)!.cast<String>(),
extra: (result[4] as Map<Object?, Object?>?)!.cast<String, Object?>(), mimeType: result[4] as String?,
extra: (result[5] as Map<Object?, Object?>?)!.cast<String, Object?>(),
); );
} }
@@ -5,6 +5,7 @@ class Intent {
final String? action; final String? action;
final String? data; final String? data;
final List<String> categories; final List<String> categories;
final String? mimeType;
final Map<String, Object?> extra; final Map<String, Object?> extra;
Intent({ Intent({
@@ -12,6 +13,7 @@ class Intent {
required this.action, required this.action,
required this.data, required this.data,
required this.categories, required this.categories,
required this.mimeType,
required this.extra, required this.extra,
}); });
} }