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 '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));
@@ -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<String>()
intent.categories?.let {
categories.addAll(it)
}
// Extract extras
val extras = HashMap<String, Any?>()
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<String, Any?>()
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
)
}
@@ -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<String>,
val mimeType: String? = null,
val extra: Map<String, Any?>
)
{
@@ -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<String>
val extra = pigeonVar_list[4] as Map<String, Any?>
return Intent(fromPackageName, action, data, categories, extra)
val mimeType = pigeonVar_list[4] as String?
val extra = pigeonVar_list[5] as Map<String, Any?>
return Intent(fromPackageName, action, data, categories, mimeType, extra)
}
}
fun toList(): List<Any?> {
@@ -86,6 +88,7 @@ data class Intent (
action,
data,
categories,
mimeType,
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
// 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<String> categories;
String? mimeType;
Map<String, Object?> extra;
List<Object?> _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<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? data;
final List<String> categories;
final String? mimeType;
final Map<String, Object?> extra;
Intent({
@@ -12,6 +13,7 @@ class Intent {
required this.action,
required this.data,
required this.categories,
required this.mimeType,
required this.extra,
});
}