diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2c1bcc8a..f5519768f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.
- switching to PiP when changing device orientation on Android >=13
- handling wallpaper intent without URI
+- sizing widgets with some launchers on Android >=12
## [v1.11.3] - 2024-06-17
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt
index 3fa77730b..27a70d25f 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt
@@ -12,6 +12,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
+import android.util.SizeF
import android.widget.RemoteViews
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
@@ -42,10 +43,10 @@ class HomeWidgetProvider : AppWidgetProvider() {
defaultScope.launch {
val backgroundProps = getProps(context, widgetId, widgetInfo, drawEntryImage = false)
- updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, backgroundProps)
+ updateWidgetImage(context, appWidgetManager, widgetId, backgroundProps)
val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = false)
- updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageProps)
+ updateWidgetImage(context, appWidgetManager, widgetId, imageProps)
}
}
}
@@ -61,20 +62,32 @@ class HomeWidgetProvider : AppWidgetProvider() {
imageByteFetchJob = defaultScope.launch {
delay(500)
val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = true)
- updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageProps)
+ updateWidgetImage(context, appWidgetManager, widgetId, imageProps)
}
}
private fun getDevicePixelRatio(): Float = Resources.getSystem().displayMetrics.density
- private fun getWidgetSizePx(context: Context, widgetInfo: Bundle): Pair {
- val devicePixelRatio = getDevicePixelRatio()
- val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
- val widthKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
- val heightKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
- val widthPx = (widgetInfo.getInt(widthKey) * devicePixelRatio).roundToInt()
- val heightPx = (widgetInfo.getInt(heightKey) * devicePixelRatio).roundToInt()
- return Pair(widthPx, heightPx)
+ private fun getWidgetSizesDip(context: Context, widgetInfo: Bundle): List {
+ var sizes: List? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ widgetInfo.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, SizeF::class.java)
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ @Suppress("DEPRECATION")
+ widgetInfo.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES)
+ } else {
+ null
+ }
+
+ if (sizes.isNullOrEmpty()) {
+ val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
+ val widthKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
+ val heightKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
+ val widthDip = widgetInfo.getInt(widthKey)
+ val heightDip = widgetInfo.getInt(heightKey)
+ sizes = listOf(SizeF(widthDip.toFloat(), heightDip.toFloat()))
+ }
+
+ return sizes.map { size -> hashMapOf("widthDip" to size.width, "heightDip" to size.height) }
}
private suspend fun getProps(
@@ -84,8 +97,11 @@ class HomeWidgetProvider : AppWidgetProvider() {
drawEntryImage: Boolean,
reuseEntry: Boolean = false,
): FieldMap? {
- val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo)
- if (widthPx == 0 || heightPx == 0) return null
+ val sizesDip = getWidgetSizesDip(context, widgetInfo)
+ if (sizesDip.isEmpty()) return null
+
+ val sizeDip = sizesDip.first()
+ if (sizeDip["widthDip"] == 0 || sizeDip["heightDip"] == 0) return null
val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
@@ -98,8 +114,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
FlutterUtils.runOnUiThread {
channel.invokeMethod("drawWidget", hashMapOf(
"widgetId" to widgetId,
- "widthPx" to widthPx,
- "heightPx" to heightPx,
+ "sizesDip" to sizesDip,
"devicePixelRatio" to getDevicePixelRatio(),
"drawEntryImage" to drawEntryImage,
"reuseEntry" to reuseEntry,
@@ -127,7 +142,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
@Suppress("unchecked_cast")
return props as FieldMap?
} catch (e: Exception) {
- Log.e(LOG_TAG, "failed to draw widget for widgetId=$widgetId widthPx=$widthPx heightPx=$heightPx", e)
+ Log.e(LOG_TAG, "failed to draw widget for widgetId=$widgetId sizesPx=$sizesDip", e)
}
return null
}
@@ -136,36 +151,83 @@ class HomeWidgetProvider : AppWidgetProvider() {
context: Context,
appWidgetManager: AppWidgetManager,
widgetId: Int,
- widgetInfo: Bundle,
props: FieldMap?,
) {
props ?: return
- val bytes = props["bytes"] as ByteArray?
+ val bytesBySizeDip = (props["bytesBySizeDip"] as List<*>?)?.mapNotNull {
+ if (it is Map<*, *>) {
+ val widthDip = (it["widthDip"] as Number?)?.toFloat()
+ val heightDip = (it["heightDip"] as Number?)?.toFloat()
+ val bytes = it["bytes"] as ByteArray?
+ if (widthDip != null && heightDip != null && bytes != null) {
+ Pair(SizeF(widthDip, heightDip), bytes)
+ } else null
+ } else null
+ }
val updateOnTap = props["updateOnTap"] as Boolean?
- if (bytes == null || updateOnTap == null) {
+ if (bytesBySizeDip == null || updateOnTap == null) {
Log.e(LOG_TAG, "missing arguments")
return
}
- val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo)
- if (widthPx == 0 || heightPx == 0) return
+ if (bytesBySizeDip.isEmpty()) {
+ Log.e(LOG_TAG, "empty image list")
+ return
+ }
+
+ val bitmaps = ArrayList()
+
+ fun createRemoteViewsForSize(
+ context: Context,
+ widgetId: Int,
+ sizeDip: SizeF,
+ bytes: ByteArray,
+ updateOnTap: Boolean,
+ ): RemoteViews? {
+ val devicePixelRatio = getDevicePixelRatio()
+ val widthPx = (sizeDip.width * devicePixelRatio).roundToInt()
+ val heightPx = (sizeDip.height * devicePixelRatio).roundToInt()
+
+ try {
+ val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888).also {
+ bitmaps.add(it)
+ it.copyPixelsFromBuffer(ByteBuffer.wrap(bytes))
+ }
+
+ val pendingIntent = if (updateOnTap) buildUpdateIntent(context, widgetId) else buildOpenAppIntent(context, widgetId)
+
+ return RemoteViews(context.packageName, R.layout.app_widget).apply {
+ setImageViewBitmap(R.id.widget_img, bitmap)
+ setOnClickPendingIntent(R.id.widget_img, pendingIntent)
+ }
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "failed to draw widget", e)
+ }
+ return null
+ }
try {
- val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
- bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(bytes))
-
- val pendingIntent = if (updateOnTap) buildUpdateIntent(context, widgetId) else buildOpenAppIntent(context, widgetId)
-
- val views = RemoteViews(context.packageName, R.layout.app_widget).apply {
- setImageViewBitmap(R.id.widget_img, bitmap)
- setOnClickPendingIntent(R.id.widget_img, pendingIntent)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ // multiple rendering for all possible sizes
+ val views = RemoteViews(
+ bytesBySizeDip.associateBy(
+ { (sizeDip, _) -> sizeDip },
+ { (sizeDip, bytes) -> createRemoteViewsForSize(context, widgetId, sizeDip, bytes, updateOnTap) },
+ ).filterValues { it != null }.mapValues { (_, view) -> view!! }
+ )
+ appWidgetManager.updateAppWidget(widgetId, views)
+ } else {
+ // single rendering
+ val (sizeDip, bytes) = bytesBySizeDip.first()
+ val views = createRemoteViewsForSize(context, widgetId, sizeDip, bytes, updateOnTap)
+ appWidgetManager.updateAppWidget(widgetId, views)
}
-
- appWidgetManager.updateAppWidget(widgetId, views)
- bitmap.recycle()
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to draw widget", e)
+ } finally {
+ bitmaps.forEach { it.recycle() }
+ bitmaps.clear()
}
}
diff --git a/lib/widget_common.dart b/lib/widget_common.dart
index bf115cd07..34a8067cb 100644
--- a/lib/widget_common.dart
+++ b/lib/widget_common.dart
@@ -38,8 +38,9 @@ void widgetMainCommon(AppFlavor flavor) async {
Future