From 27db528e67814c2eda33699a771aa54c0fa70e8c Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 29 Jun 2024 01:29:22 +0200 Subject: [PATCH] fixed handling wallpaper intent without uri #1052 handle secure review intent --- CHANGELOG.md | 1 + android/app/src/main/AndroidManifest.xml | 3 + .../deckers/thibault/aves/MainActivity.kt | 24 ++- .../thibault/aves/WallpaperActivity.kt | 153 +++++------------- lib/widgets/home_page.dart | 5 +- lib/widgets/intent.dart | 2 + 6 files changed, 69 insertions(+), 119 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6146aedac..b2c1bcc8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. ### Fixed - switching to PiP when changing device orientation on Android >=13 +- handling wallpaper intent without URI ## [v1.11.3] - 2024-06-17 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 67ad18af7..6d1cb4a88 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -120,6 +120,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher_round" + android:showWhenLocked="true" android:supportsRtl="true" tools:targetApi="tiramisu"> + @@ -163,6 +165,7 @@ + diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt index 96dd14303..339ecbbbf 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -36,6 +36,7 @@ import deckers.thibault.aves.channel.calls.MetadataEditHandler import deckers.thibault.aves.channel.calls.MetadataFetchHandler import deckers.thibault.aves.channel.calls.SecurityHandler import deckers.thibault.aves.channel.calls.StorageHandler +import deckers.thibault.aves.channel.calls.WallpaperHandler import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler import deckers.thibault.aves.channel.calls.window.WindowHandler import deckers.thibault.aves.channel.streams.ActivityResultStreamHandler @@ -135,6 +136,7 @@ open class MainActivity : FlutterFragmentActivity() { MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this)) MethodChannel(messenger, MediaEditHandler.CHANNEL).setMethodCallHandler(MediaEditHandler(this)) MethodChannel(messenger, MetadataEditHandler.CHANNEL).setMethodCallHandler(MetadataEditHandler(this)) + MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this)) // - need Activity MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(this)) @@ -301,16 +303,32 @@ open class MainActivity : FlutterFragmentActivity() { Intent.ACTION_VIEW, Intent.ACTION_SEND, MediaStore.ACTION_REVIEW, + MediaStore.ACTION_REVIEW_SECURE, "com.android.camera.action.REVIEW", "com.android.camera.action.SPLIT_SCREEN_REVIEW" -> { (intent.data ?: intent.getParcelableExtraCompat(Intent.EXTRA_STREAM))?.let { uri -> // MIME type is optional val type = intent.type ?: intent.resolveType(this) - return hashMapOf( + val fields = hashMapOf( INTENT_DATA_KEY_ACTION to INTENT_ACTION_VIEW, INTENT_DATA_KEY_MIME_TYPE to type, INTENT_DATA_KEY_URI to uri.toString(), ) + + if (action == MediaStore.ACTION_REVIEW_SECURE) { + val uris = ArrayList() + intent.clipData?.let { clipData -> + for (i in 0 until clipData.itemCount) { + clipData.getItemAt(i).uri?.let { uris.add(it.toString()) } + } + } + fields[INTENT_DATA_KEY_SECURE_URIS] = uris + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && intent.hasExtra(MediaStore.EXTRA_BRIGHTNESS)) { + fields[INTENT_DATA_KEY_BRIGHTNESS] = intent.getFloatExtra(MediaStore.EXTRA_BRIGHTNESS, 0f) + } + + return fields } } @@ -390,7 +408,7 @@ open class MainActivity : FlutterFragmentActivity() { return null } - private fun submitPickedItems(call: MethodCall) { + open fun submitPickedItems(call: MethodCall) { val pickedUris = call.argument>("uris") if (!pickedUris.isNullOrEmpty()) { val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) } @@ -498,11 +516,13 @@ open class MainActivity : FlutterFragmentActivity() { const val INTENT_DATA_KEY_ACTION = "action" const val INTENT_DATA_KEY_ALLOW_MULTIPLE = "allowMultiple" + const val INTENT_DATA_KEY_BRIGHTNESS = "brightness" const val INTENT_DATA_KEY_FILTERS = "filters" const val INTENT_DATA_KEY_MIME_TYPE = "mimeType" const val INTENT_DATA_KEY_PAGE = "page" const val INTENT_DATA_KEY_QUERY = "query" const val INTENT_DATA_KEY_SAFE_MODE = "safeMode" + const val INTENT_DATA_KEY_SECURE_URIS = "secureUris" const val INTENT_DATA_KEY_URI = "uri" const val INTENT_DATA_KEY_WIDGET_ID = "widgetId" diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt index 4aa32be1a..9adf417c4 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt @@ -2,132 +2,53 @@ package deckers.thibault.aves import android.content.Intent import android.net.Uri -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.util.Log -import app.loup.streams_channel.StreamsChannel -import deckers.thibault.aves.channel.AvesByteSendingMethodCodec -import deckers.thibault.aves.channel.calls.AccessibilityHandler -import deckers.thibault.aves.channel.calls.DeviceHandler -import deckers.thibault.aves.channel.calls.EmbeddedDataHandler -import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler -import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler -import deckers.thibault.aves.channel.calls.MediaSessionHandler -import deckers.thibault.aves.channel.calls.MetadataFetchHandler -import deckers.thibault.aves.channel.calls.StorageHandler -import deckers.thibault.aves.channel.calls.WallpaperHandler -import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler -import deckers.thibault.aves.channel.calls.window.WindowHandler -import deckers.thibault.aves.channel.streams.ImageByteStreamHandler -import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler +import deckers.thibault.aves.channel.calls.AppAdapterHandler import deckers.thibault.aves.model.FieldMap -import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.getParcelableExtraCompat -import io.flutter.embedding.android.FlutterFragmentActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -class WallpaperActivity : FlutterFragmentActivity() { - private lateinit var intentDataMap: FieldMap - private lateinit var mediaSessionHandler: MediaSessionHandler +class WallpaperActivity : MainActivity() { + private var originalIntent: String? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + override fun extractIntentData(intent: Intent?): FieldMap { + if (intent != null) { + when (intent.action) { + Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> { + (intent.data ?: intent.getParcelableExtraCompat(Intent.EXTRA_STREAM))?.let { uri -> + // MIME type is optional + val type = intent.type ?: intent.resolveType(this) + return hashMapOf( + INTENT_DATA_KEY_ACTION to INTENT_ACTION_SET_WALLPAPER, + INTENT_DATA_KEY_MIME_TYPE to type, + INTENT_DATA_KEY_URI to uri.toString(), + ) + } - Log.i(LOG_TAG, "onCreate intent=$intent") - intent.extras?.takeUnless { it.isEmpty }?.let { - Log.i(LOG_TAG, "onCreate intent extras=$it") - } - intentDataMap = extractIntentData(intent) - } - - override fun configureFlutterEngine(flutterEngine: FlutterEngine) { - super.configureFlutterEngine(flutterEngine) - val messenger = flutterEngine.dartExecutor - - // notification: platform -> dart - val mediaCommandStreamHandler = MediaCommandStreamHandler().apply { - EventChannel(messenger, MediaCommandStreamHandler.CHANNEL).setStreamHandler(this) - } - - // dart -> platform -> dart - // - need Context - mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler) - MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this)) - MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this)) - MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this)) - MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this)) - MethodChannel(messenger, MediaSessionHandler.CHANNEL).setMethodCallHandler(mediaSessionHandler) - MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this)) - MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this)) - // - need ContextWrapper - MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this)) - MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this)) - // - need Activity - MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(this)) - - // result streaming: dart -> platform ->->-> dart - // - need Context - StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(this, args) } - - // intent handling - // detail fetch: dart -> platform - MethodChannel(messenger, MainActivity.INTENT_CHANNEL).setMethodCallHandler { call, result -> onMethodCall(call, result) } - } - - override fun onStart() { - Log.i(LOG_TAG, "onStart") - super.onStart() - - // as of Flutter v3.0.1, the window `viewInsets` and `viewPadding` - // are incorrect on startup in some environments (e.g. API 29 emulator), - // so we manually request to apply the insets to update the window metrics - Handler(Looper.getMainLooper()).postDelayed({ - window.decorView.requestApplyInsets() - }, 100) - } - - override fun onDestroy() { - mediaSessionHandler.dispose() - super.onDestroy() - } - - private fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "getIntentData" -> { - result.success(intentDataMap) - intentDataMap.clear() - } - } - } - - private fun extractIntentData(intent: Intent?): FieldMap { - when (intent?.action) { - Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> { - (intent.data ?: intent.getParcelableExtraCompat(Intent.EXTRA_STREAM))?.let { uri -> - // MIME type is optional - val type = intent.type ?: intent.resolveType(this) - return hashMapOf( - MainActivity.INTENT_DATA_KEY_ACTION to MainActivity.INTENT_ACTION_SET_WALLPAPER, - MainActivity.INTENT_DATA_KEY_MIME_TYPE to type, - MainActivity.INTENT_DATA_KEY_URI to uri.toString(), - ) + // if the media URI is not provided we need to pick one first + originalIntent = intent.action + intent.action = Intent.ACTION_PICK } } - Intent.ACTION_RUN -> { - // flutter run - } - else -> { - Log.w(LOG_TAG, "unhandled intent action=${intent?.action}") - } } - return HashMap() + + return super.extractIntentData(intent) } - companion object { - private val LOG_TAG = LogUtils.createTag() + override fun submitPickedItems(call: MethodCall) { + if (originalIntent != null) { + val pickedUris = call.argument>("uris") + if (!pickedUris.isNullOrEmpty()) { + val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) } + onNewIntent(Intent().apply { + action = originalIntent + data = toUri(pickedUris.first()) + }) + } else { + setResult(RESULT_CANCELED) + finish() + } + } else { + super.submitPickedItems(call) + } } } diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 42273d101..64fc2ae08 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -61,6 +61,7 @@ class _HomePageState extends State { int? _widgetId; String? _initialRouteName, _initialSearchQuery; Set? _initialFilters; + List? _secureUris; static const allowedShortcutRoutes = [ CollectionPage.routeName, @@ -91,6 +92,7 @@ class _HomePageState extends State { final safeMode = intentData[IntentDataKeys.safeMode] ?? false; final intentAction = intentData[IntentDataKeys.action]; _initialFilters = null; + _secureUris = null; await androidFileUtils.init(); if (!{ @@ -127,6 +129,7 @@ class _HomePageState extends State { uri = intentData[IntentDataKeys.uri]; mimeType = intentData[IntentDataKeys.mimeType]; } + _secureUris = intentData[IntentDataKeys.secureUris]; if (uri != null) { _viewerEntry = await _initViewerEntry( uri: uri, @@ -208,7 +211,7 @@ class _HomePageState extends State { canAnalyze: false, ); case AppMode.view: - if (_isViewerSourceable(_viewerEntry)) { + if (_isViewerSourceable(_viewerEntry) && _secureUris == null) { final directory = _viewerEntry?.directory; if (directory != null) { unawaited(AnalysisService.registerCallback()); diff --git a/lib/widgets/intent.dart b/lib/widgets/intent.dart index 639cd5526..e0a97587e 100644 --- a/lib/widgets/intent.dart +++ b/lib/widgets/intent.dart @@ -14,11 +14,13 @@ class IntentActions { class IntentDataKeys { static const action = 'action'; static const allowMultiple = 'allowMultiple'; + static const brightness = 'brightness'; static const filters = 'filters'; static const mimeType = 'mimeType'; static const page = 'page'; static const query = 'query'; static const safeMode = 'safeMode'; + static const secureUris = 'secureUris'; static const uri = 'uri'; static const widgetId = 'widgetId'; }