#1110 fixed widget setup race

This commit is contained in:
Thibault Deckers 2024-08-04 17:16:26 +02:00
parent 0bbed0ebda
commit 3bad4b6814
5 changed files with 71 additions and 34 deletions

View file

@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.
- Viewer: display more items in tag/copy/move quick action choosers - Viewer: display more items in tag/copy/move quick action choosers
### Fixed
- multiple widget setup after device reboot
## <a id="v1.11.8"></a>[v1.11.8] - 2024-07-19 ## <a id="v1.11.8"></a>[v1.11.8] - 2024-07-19
### Added ### Added

View file

@ -11,12 +11,18 @@ import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log import android.util.Log
import android.util.SizeF import android.util.SizeF
import android.widget.RemoteViews import android.widget.RemoteViews
import app.loup.streams_channel.StreamsChannel import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.* import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
import deckers.thibault.aves.channel.calls.MediaStoreHandler
import deckers.thibault.aves.channel.calls.StorageHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.model.FieldMap
@ -26,8 +32,14 @@ import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.nio.ByteBuffer import java.nio.ByteBuffer
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@ -108,14 +120,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
initFlutterEngine(context) val params = hashMapOf(
val messenger = flutterEngine!!.dartExecutor
val channel = MethodChannel(messenger, WIDGET_DRAW_CHANNEL)
try {
val props = suspendCoroutine<Any?> { cont ->
defaultScope.launch {
FlutterUtils.runOnUiThread {
channel.invokeMethod("drawWidget", hashMapOf(
"widgetId" to widgetId, "widgetId" to widgetId,
"sizesDip" to sizesDip, "sizesDip" to sizesDip,
"devicePixelRatio" to getDevicePixelRatio(), "devicePixelRatio" to getDevicePixelRatio(),
@ -126,19 +131,14 @@ class HomeWidgetProvider : AppWidgetProvider() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
put("cornerRadiusPx", context.resources.getDimension(android.R.dimen.system_app_widget_background_radius)) put("cornerRadiusPx", context.resources.getDimension(android.R.dimen.system_app_widget_background_radius))
} }
}, object : MethodChannel.Result {
override fun success(result: Any?) {
cont.resume(result)
} }
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { initFlutterEngine(context)
cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails")) try {
} val props = suspendCoroutine { cont ->
defaultScope.launch {
override fun notImplemented() { FlutterUtils.runOnUiThread {
cont.resumeWithException(Exception("not implemented")) tryDrawWidget(params, cont, 0)
}
})
} }
} }
} }
@ -150,6 +150,30 @@ class HomeWidgetProvider : AppWidgetProvider() {
return null return null
} }
private fun tryDrawWidget(params: HashMap<String, Any>, cont: Continuation<Any?>, drawRetry: Int) {
val messenger = flutterEngine!!.dartExecutor
val channel = MethodChannel(messenger, WIDGET_DRAW_CHANNEL)
channel.invokeMethod("drawWidget", params, object : MethodChannel.Result {
override fun success(result: Any?) {
cont.resume(result)
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails"))
}
override fun notImplemented() {
if (drawRetry > DRAW_RETRY_MAX) {
cont.resumeWithException(Exception("not implemented"))
} else {
Handler(Looper.getMainLooper()).postDelayed({
tryDrawWidget(params, cont, drawRetry + 1)
}, 2000L)
}
}
})
}
private fun updateWidgetImage( private fun updateWidgetImage(
context: Context, context: Context,
appWidgetManager: AppWidgetManager, appWidgetManager: AppWidgetManager,
@ -271,6 +295,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
private val LOG_TAG = LogUtils.createTag<HomeWidgetProvider>() private val LOG_TAG = LogUtils.createTag<HomeWidgetProvider>()
private const val WIDGET_DART_ENTRYPOINT = "widgetMain" private const val WIDGET_DART_ENTRYPOINT = "widgetMain"
private const val WIDGET_DRAW_CHANNEL = "deckers.thibault/aves/widget_draw" private const val WIDGET_DRAW_CHANNEL = "deckers.thibault/aves/widget_draw"
private const val DRAW_RETRY_MAX = 5
private var flutterEngine: FlutterEngine? = null private var flutterEngine: FlutterEngine? = null
private var imageByteFetchJob: Job? = null private var imageByteFetchJob: Job? = null

View file

@ -7,6 +7,8 @@ import 'package:flutter/foundation.dart';
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private(); final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
enum _State { uninitialized, initializing, initialized }
class AndroidFileUtils { class AndroidFileUtils {
// cf https://developer.android.com/reference/android/content/ContentResolver#SCHEME_CONTENT // cf https://developer.android.com/reference/android/content/ContentResolver#SCHEME_CONTENT
static const contentScheme = 'content'; static const contentScheme = 'content';
@ -27,13 +29,19 @@ class AndroidFileUtils {
late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath; late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath;
late final Set<String> videoCapturesPaths; late final Set<String> videoCapturesPaths;
Set<StorageVolume> storageVolumes = {}; Set<StorageVolume> storageVolumes = {};
bool _initialized = false; _State _initialized = _State.uninitialized;
AndroidFileUtils._private(); AndroidFileUtils._private();
Future<void> init() async { Future<void> init() async {
if (_initialized) return; if (_initialized == _State.uninitialized) {
_initialized = _State.initializing;
await _doInit();
_initialized = _State.initialized;
}
}
Future<void> _doInit() async {
separator = pContext.separator; separator = pContext.separator;
await _initStorageVolumes(); await _initStorageVolumes();
vaultRoot = await storageService.getVaultRoot(); vaultRoot = await storageService.getVaultRoot();
@ -50,8 +58,6 @@ class AndroidFileUtils {
// from Aves // from Aves
avesVideoCapturesPath, avesVideoCapturesPath,
}; };
_initialized = true;
} }
Future<void> _initStorageVolumes() async { Future<void> _initStorageVolumes() async {

View file

@ -18,11 +18,13 @@ import 'package:flutter/services.dart';
const _widgetDrawChannel = MethodChannel('deckers.thibault/aves/widget_draw'); const _widgetDrawChannel = MethodChannel('deckers.thibault/aves/widget_draw');
void widgetMainCommon(AppFlavor flavor) async { void widgetMainCommon(AppFlavor flavor) async {
debugPrint('Widget main start');
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
initPlatformServices(); initPlatformServices();
await settings.init(monitorPlatformSettings: false); await settings.init(monitorPlatformSettings: false);
await reportService.init(); await reportService.init();
debugPrint('Widget channel method handling setup');
_widgetDrawChannel.setMethodCallHandler((call) async { _widgetDrawChannel.setMethodCallHandler((call) async {
// widget settings may be modified in a different process after channel setup // widget settings may be modified in a different process after channel setup
await settings.reload(); await settings.reload();

View file

@ -36,7 +36,7 @@ class HomeWidgetPainter {
final widthPx = sizeDip.width * devicePixelRatio; final widthPx = sizeDip.width * devicePixelRatio;
final heightPx = sizeDip.height * devicePixelRatio; final heightPx = sizeDip.height * devicePixelRatio;
final widgetSizePx = Size(widthPx, heightPx); final widgetSizePx = Size(widthPx, heightPx);
debugPrint('draw widget for $sizeDip dp ($widgetSizePx px), entry=$entry'); debugPrint('Draw widget for ${sizeDip.width}x${sizeDip.height} dp (${widgetSizePx.width}x${widgetSizePx.height} px), entry=$entry');
final ui.Image? entryImage; final ui.Image? entryImage;
if (entry != null) { if (entry != null) {
final extent = shape.extentPx(widgetSizePx, entry!) / devicePixelRatio; final extent = shape.extentPx(widgetSizePx, entry!) / devicePixelRatio;