Widget: render mic+badge as bitmaps (Lawnchair themed-icons flattened drawable resources to gray)

This commit is contained in:
avi 2026-09-17 20:51:19 -05:00
commit 7f9b9c2e5c

View file

@ -47,22 +47,41 @@ class ShonarRecordWidget : AppWidgetProvider() {
companion object {
const val ACTION_WIDGET_RECORD = "com.shonar.widget.RECORD_TAP"
/**
* Render the vector to a bitmap instead of handing the launcher a
* drawable resource id: themed-icon launchers (Lawnchair) re-tint any
* resource they can resolve, which flattened the navy+teal mic to a
* gray monochrome silhouette. A bitmap is opaque pixels — nothing to
* tint. (2026-09-17)
*/
private fun bitmapOf(ctx: Context, resId: Int, dp: Int): android.graphics.Bitmap {
val size = (dp * ctx.resources.displayMetrics.density).toInt().coerceAtLeast(64)
val d = androidx.core.content.ContextCompat.getDrawable(ctx, resId)
?: throw IllegalStateException("missing widget drawable $resId")
val bmp =
android.graphics.Bitmap.createBitmap(size, size, android.graphics.Bitmap.Config.ARGB_8888)
val canvas = android.graphics.Canvas(bmp)
d.setBounds(0, 0, size, size)
d.draw(canvas)
return bmp
}
fun views(
ctx: Context,
phase: com.shonar.recording.RecordingSnapshot.Phase,
): RemoteViews {
val v = RemoteViews(ctx.packageName, R.layout.shonar_widget)
v.setImageViewResource(R.id.widget_icon, R.drawable.ic_widget_mic)
v.setImageViewBitmap(R.id.widget_icon, bitmapOf(ctx, R.drawable.ic_widget_mic, 96))
when (phase) {
com.shonar.recording.RecordingSnapshot.Phase.RECORDING -> {
v.setImageViewResource(R.id.widget_badge, R.drawable.ic_widget_badge_rec)
v.setImageViewBitmap(R.id.widget_badge, bitmapOf(ctx, R.drawable.ic_widget_badge_rec, 28))
v.setViewVisibility(R.id.widget_badge, View.VISIBLE)
v.setContentDescription(
R.id.widget_button, ctx.getString(R.string.widget_recording),
)
}
com.shonar.recording.RecordingSnapshot.Phase.PAUSED -> {
v.setImageViewResource(R.id.widget_badge, R.drawable.ic_widget_badge_paused)
v.setImageViewBitmap(R.id.widget_badge, bitmapOf(ctx, R.drawable.ic_widget_badge_paused, 28))
v.setViewVisibility(R.id.widget_badge, View.VISIBLE)
v.setContentDescription(
R.id.widget_button, ctx.getString(R.string.widget_paused),
@ -77,10 +96,14 @@ class ShonarRecordWidget : AppWidgetProvider() {
}
val tap: PendingIntent = if (phase == com.shonar.recording.RecordingSnapshot.Phase.IDLE) {
PendingIntent.getBroadcast(
// Invisible activity trampoline, NOT a broadcast: a cold
// process woken by a broadcast is battery-throttled (the
// banner could take ~10s); an activity start runs at full
// priority and gives startForegroundService its FGS
// exemption on Android 12+.
PendingIntent.getActivity(
ctx, 50,
Intent(ctx, ShonarRecordWidget::class.java).setAction(ACTION_WIDGET_RECORD)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
Intent(ctx, StartRecordingActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
} else {