get result uri from edit intent
This commit is contained in:
parent
a0159c9b82
commit
28f7819eaf
5 changed files with 100 additions and 29 deletions
|
@ -21,6 +21,7 @@ import deckers.thibault.aves.channel.calls.*
|
|||
import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
|
||||
import deckers.thibault.aves.channel.calls.window.WindowHandler
|
||||
import deckers.thibault.aves.channel.streams.*
|
||||
import deckers.thibault.aves.model.FieldMap
|
||||
import deckers.thibault.aves.utils.FlutterUtils.enableSoftwareRendering
|
||||
import deckers.thibault.aves.utils.FlutterUtils.isSoftwareRenderingRequired
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
|
@ -218,6 +219,7 @@ open class MainActivity : FlutterFragmentActivity() {
|
|||
OPEN_FILE_REQUEST -> onStorageAccessResult(requestCode, data?.data)
|
||||
|
||||
PICK_COLLECTION_FILTERS_REQUEST -> onCollectionFiltersPickResult(resultCode, data)
|
||||
EDIT_REQUEST -> onEditResult(resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,6 +228,14 @@ open class MainActivity : FlutterFragmentActivity() {
|
|||
pendingCollectionFilterPickHandler?.let { it(filters) }
|
||||
}
|
||||
|
||||
private fun onEditResult(resultCode: Int, intent: Intent?) {
|
||||
val fields: FieldMap? = if (resultCode == RESULT_OK) hashMapOf(
|
||||
"uri" to intent?.data.toString(),
|
||||
"mimeType" to intent?.type,
|
||||
) else null
|
||||
pendingEditIntentHandler?.let { it(fields) }
|
||||
}
|
||||
|
||||
private fun onDocumentTreeAccessResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
val treeUri = intent?.data
|
||||
if (resultCode != RESULT_OK || treeUri == null) {
|
||||
|
@ -458,6 +468,7 @@ open class MainActivity : FlutterFragmentActivity() {
|
|||
const val DELETE_SINGLE_PERMISSION_REQUEST = 5
|
||||
const val MEDIA_WRITE_BULK_PERMISSION_REQUEST = 6
|
||||
const val PICK_COLLECTION_FILTERS_REQUEST = 7
|
||||
const val EDIT_REQUEST = 8
|
||||
|
||||
const val INTENT_ACTION_EDIT = "edit"
|
||||
const val INTENT_ACTION_PICK_ITEMS = "pick_items"
|
||||
|
@ -493,6 +504,8 @@ open class MainActivity : FlutterFragmentActivity() {
|
|||
|
||||
var pendingCollectionFilterPickHandler: ((filters: List<String>?) -> Unit)? = null
|
||||
|
||||
var pendingEditIntentHandler: ((fields: FieldMap?) -> Unit)? = null
|
||||
|
||||
private fun onStorageAccessResult(requestCode: Int, uri: Uri?) {
|
||||
Log.i(LOG_TAG, "onStorageAccessResult with requestCode=$requestCode, uri=$uri")
|
||||
val handler = pendingStorageAccessResultHandlers.remove(requestCode) ?: return
|
||||
|
|
|
@ -52,7 +52,6 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
"getPackages" -> ioScope.launch { safe(call, result, ::getPackages) }
|
||||
"getAppIcon" -> ioScope.launch { safeSuspend(call, result, ::getAppIcon) }
|
||||
"copyToClipboard" -> ioScope.launch { safe(call, result, ::copyToClipboard) }
|
||||
"edit" -> safe(call, result, ::edit)
|
||||
"open" -> safe(call, result, ::open)
|
||||
"openMap" -> safe(call, result, ::openMap)
|
||||
"setAs" -> safe(call, result, ::setAs)
|
||||
|
@ -207,22 +206,6 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private fun edit(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||
val mimeType = call.argument<String>("mimeType")
|
||||
if (uri == null) {
|
||||
result.error("edit-args", "missing arguments", null)
|
||||
return
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_EDIT)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
.setDataAndType(getShareableUri(context, uri), mimeType)
|
||||
val started = safeStartActivity(intent)
|
||||
|
||||
result.success(started)
|
||||
}
|
||||
|
||||
private fun open(call: MethodCall, result: MethodChannel.Result) {
|
||||
val title = call.argument<String>("title")
|
||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||
|
@ -404,6 +387,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
|
||||
// so we use a joined `String` as fallback
|
||||
.putExtra(EXTRA_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR))
|
||||
|
||||
else -> {
|
||||
result.error("pin-intent", "failed to build intent", null)
|
||||
return
|
||||
|
@ -434,6 +418,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
FileProvider.getUriForFile(context, authority, File(path))
|
||||
}
|
||||
}
|
||||
|
||||
else -> uri
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.os.Looper
|
|||
import android.util.Log
|
||||
import deckers.thibault.aves.MainActivity
|
||||
import deckers.thibault.aves.PendingStorageAccessResultHandler
|
||||
import deckers.thibault.aves.channel.calls.AppAdapterHandler
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
import deckers.thibault.aves.utils.MimeTypes
|
||||
import deckers.thibault.aves.utils.PermissionManager
|
||||
|
@ -47,6 +48,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
|
|||
"requestMediaFileAccess" -> ioScope.launch { requestMediaFileAccess() }
|
||||
"createFile" -> ioScope.launch { createFile() }
|
||||
"openFile" -> ioScope.launch { openFile() }
|
||||
"edit" -> edit()
|
||||
"pickCollectionFilters" -> pickCollectionFilters()
|
||||
else -> endOfStream()
|
||||
}
|
||||
|
@ -100,10 +102,13 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
|
|||
endOfStream()
|
||||
}
|
||||
|
||||
private suspend fun safeStartActivityForResult(intent: Intent, requestCode: Int, onGranted: (uri: Uri) -> Unit, onDenied: () -> Unit) {
|
||||
private suspend fun safeStartActivityForStorageAccessResult(intent: Intent, requestCode: Int, onGranted: (uri: Uri) -> Unit, onDenied: () -> Unit) {
|
||||
if (intent.resolveActivity(activity.packageManager) != null) {
|
||||
MainActivity.pendingStorageAccessResultHandlers[requestCode] = PendingStorageAccessResultHandler(null, onGranted, onDenied)
|
||||
activity.startActivityForResult(intent, requestCode)
|
||||
if (!safeStartActivityForResult(intent, requestCode)) {
|
||||
MainActivity.notifyError("failed to start activity for intent=$intent extras=${intent.extras}")
|
||||
onDenied()
|
||||
}
|
||||
} else {
|
||||
MainActivity.notifyError("failed to resolve activity for intent=$intent extras=${intent.extras}")
|
||||
onDenied()
|
||||
|
@ -144,7 +149,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
|
|||
type = mimeType
|
||||
putExtra(Intent.EXTRA_TITLE, name)
|
||||
}
|
||||
safeStartActivityForResult(intent, MainActivity.CREATE_FILE_REQUEST, ::onGranted, ::onDenied)
|
||||
safeStartActivityForStorageAccessResult(intent, MainActivity.CREATE_FILE_REQUEST, ::onGranted, ::onDenied)
|
||||
}
|
||||
|
||||
private suspend fun openFile() {
|
||||
|
@ -177,7 +182,33 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
|
|||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
setTypeAndNormalize(mimeType ?: MimeTypes.ANY)
|
||||
}
|
||||
safeStartActivityForResult(intent, MainActivity.OPEN_FILE_REQUEST, ::onGranted, ::onDenied)
|
||||
safeStartActivityForStorageAccessResult(intent, MainActivity.OPEN_FILE_REQUEST, ::onGranted, ::onDenied)
|
||||
}
|
||||
|
||||
private fun edit() {
|
||||
val uri = args["uri"] as String?
|
||||
val mimeType = args["mimeType"] as String? // optional
|
||||
if (uri == null) {
|
||||
error("edit-args", "missing arguments", null)
|
||||
return
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_EDIT)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
.setDataAndType(AppAdapterHandler.getShareableUri(activity, Uri.parse(uri)), mimeType)
|
||||
|
||||
if (intent.resolveActivity(activity.packageManager) == null) {
|
||||
error("edit-resolve", "cannot resolve activity for this intent", null)
|
||||
return
|
||||
}
|
||||
|
||||
MainActivity.pendingEditIntentHandler = { fields ->
|
||||
success(fields)
|
||||
endOfStream()
|
||||
}
|
||||
if (!safeStartActivityForResult(intent, MainActivity.EDIT_REQUEST)) {
|
||||
error("edit-start", "cannot start activity for this intent", null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun pickCollectionFilters() {
|
||||
|
@ -192,6 +223,24 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
|
|||
activity.startActivityForResult(intent, MainActivity.PICK_COLLECTION_FILTERS_REQUEST)
|
||||
}
|
||||
|
||||
private fun safeStartActivityForResult(intent: Intent, requestCode: Int): Boolean {
|
||||
return try {
|
||||
activity.startActivityForResult(intent, requestCode)
|
||||
true
|
||||
} catch (e: SecurityException) {
|
||||
if (intent.flags and Intent.FLAG_GRANT_WRITE_URI_PERMISSION != 0) {
|
||||
// in some environments, providing the write flag yields a `SecurityException`:
|
||||
// "UID XXXX does not have permission to content://XXXX"
|
||||
// so we retry without it
|
||||
Log.i(LOG_TAG, "retry intent=$intent without FLAG_GRANT_WRITE_URI_PERMISSION")
|
||||
intent.flags = intent.flags and Intent.FLAG_GRANT_WRITE_URI_PERMISSION.inv()
|
||||
safeStartActivityForResult(intent, requestCode)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
Log.i(LOG_TAG, "onCancel arguments=$arguments")
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/apps.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
|
@ -7,6 +9,7 @@ import 'package:aves/utils/math_utils.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:streams_channel/streams_channel.dart';
|
||||
|
||||
abstract class AppService {
|
||||
Future<Set<Package>> getPackages();
|
||||
|
@ -15,7 +18,7 @@ abstract class AppService {
|
|||
|
||||
Future<bool> copyToClipboard(String uri, String? label);
|
||||
|
||||
Future<bool> edit(String uri, String mimeType);
|
||||
Future<Map<String, dynamic>> edit(String uri, String mimeType);
|
||||
|
||||
Future<bool> open(String uri, String mimeType, {required bool forceChooser});
|
||||
|
||||
|
@ -32,6 +35,7 @@ abstract class AppService {
|
|||
|
||||
class PlatformAppService implements AppService {
|
||||
static const _platform = MethodChannel('deckers.thibault/aves/app');
|
||||
static final _stream = StreamsChannel('deckers.thibault/aves/activity_result_stream');
|
||||
|
||||
static final _knownAppDirs = {
|
||||
'com.kakao.talk': {'KakaoTalkDownload'},
|
||||
|
@ -89,17 +93,29 @@ class PlatformAppService implements AppService {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<bool> edit(String uri, String mimeType) async {
|
||||
Future<Map<String, dynamic>> edit(String uri, String mimeType) async {
|
||||
try {
|
||||
final result = await _platform.invokeMethod('edit', <String, dynamic>{
|
||||
final completer = Completer<Map?>();
|
||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'edit',
|
||||
'uri': uri,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
if (result != null) return result as bool;
|
||||
}).listen(
|
||||
(data) => completer.complete(data as Map?),
|
||||
onError: completer.completeError,
|
||||
onDone: () {
|
||||
if (!completer.isCompleted) completer.complete({'error': 'cancelled'});
|
||||
},
|
||||
cancelOnError: true,
|
||||
);
|
||||
// `await` here, so that `completeError` will be caught below
|
||||
final result = await completer.future;
|
||||
if (result == null) return {'error': 'cancelled'};
|
||||
return result.cast<String, dynamic>();
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
return {'error': e.code};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -242,8 +242,16 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
).dispatch(context);
|
||||
}
|
||||
case EntryAction.edit:
|
||||
appService.edit(targetEntry.uri, targetEntry.mimeType).then((success) {
|
||||
if (!success) showNoMatchingAppDialog(context);
|
||||
appService.edit(targetEntry.uri, targetEntry.mimeType).then((fields) {
|
||||
final error = fields['error'] as String?;
|
||||
if (error == null) {
|
||||
final uri = fields['uri'] as String?;
|
||||
if (uri != null) {
|
||||
debugPrint('TLAD uri=$uri');
|
||||
}
|
||||
} else if (error == 'edit-resolve') {
|
||||
showNoMatchingAppDialog(context);
|
||||
}
|
||||
});
|
||||
case EntryAction.open:
|
||||
appService.open(targetEntry.uri, targetEntry.mimeTypeAnySubtype, forceChooser: true).then((success) {
|
||||
|
|
Loading…
Reference in a new issue