widget: mitigate bitmap size calculation bug

This commit is contained in:
Alexander Capehart 2024-07-29 19:10:19 -06:00
parent e351a91a9c
commit 9299e03e95
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 50 additions and 14 deletions

View file

@ -285,8 +285,9 @@ private class MediaStoreExtractorImpl(
// know when it corresponds to the folder and not, say, Low Roar's breakout album "0"? // know when it corresponds to the folder and not, say, Low Roar's breakout album "0"?
// Also, on some devices it's literally just null. To maintain behavior sanity just // Also, on some devices it's literally just null. To maintain behavior sanity just
// replicate the majority behavior described prior. // replicate the majority behavior described prior.
rawSong.albumName = cursor.getStringOrNull(albumIndex) rawSong.albumName =
?: requireNotNull(rawSong.path?.name) { "Invalid raw: No path" } cursor.getStringOrNull(albumIndex)
?: requireNotNull(rawSong.path?.name) { "Invalid raw: No path" }
// Android does not make a non-existent artist tag null, it instead fills it in // Android does not make a non-existent artist tag null, it instead fills it in
// as <unknown>, which makes absolutely no sense given how other columns default // as <unknown>, which makes absolutely no sense given how other columns default
// to null if they are not present. If this column is such, null it so that // to null if they are not present. If this column is such, null it so that

View file

@ -101,17 +101,51 @@ class WidgetProvider : AppWidgetProvider() {
SizeF(180f, 272f) to newThinPaneLayout(context, uiSettings, state), SizeF(180f, 272f) to newThinPaneLayout(context, uiSettings, state),
SizeF(304f, 272f) to newWidePaneLayout(context, uiSettings, state)) SizeF(304f, 272f) to newWidePaneLayout(context, uiSettings, state))
// This is the order in which we will disable cover art layouts if they exceed the
// maximum bitmap memory usage. (See the comment in the loop below for more info.)
val victims =
mutableSetOf(
R.layout.widget_wafer_thin,
R.layout.widget_wafer_wide,
R.layout.widget_pane_thin,
R.layout.widget_pane_wide,
R.layout.widget_docked_thin,
R.layout.widget_docked_wide,
)
// Manually update AppWidgetManager with the new views. // Manually update AppWidgetManager with the new views.
val awm = AppWidgetManager.getInstance(context) val awm = AppWidgetManager.getInstance(context)
val component = ComponentName(context, this::class.java) val component = ComponentName(context, this::class.java)
try { while (victims.size > 0) {
awm.updateAppWidgetCompat(context, component, views) try {
logD("Successfully updated RemoteViews layout") awm.updateAppWidgetCompat(context, component, views)
} catch (e: Exception) { logD("Successfully updated RemoteViews layout")
// Layout update failed, gracefully degrade to the default widget. return
logW("Unable to update widget: $e") } catch (e: IllegalArgumentException) {
reset(context, uiSettings) val msg = e.message ?: return
if (!msg.startsWith(
"RemoteViews for widget update exceeds maximum bitmap memory usage")) {
throw e
}
// Some android devices on Android 12-14 suffer from a bug where the maximum bitmap
// size calculation does not factor in bitmaps shared across multiple RemoteView
// forms.
// To mitigate an outright crash, progressively disable layouts that contain cover
// art
// in order of least to most commonly used until it actually works.
val victim = victims.first()
val view = views.entries.find { it.value.layoutId == victim } ?: continue
view.value.discardCover(context)
victims.remove(victim)
} catch (e: Exception) {
// Layout update failed, gracefully degrade to the default widget.
logW("Unable to update widget: $e")
reset(context, uiSettings)
}
} }
// We flat-out cannot fit the bitmap into the widget. Weird.
logW("Unable to update widget: Bitmap too large")
reset(context, uiSettings)
} }
/** /**
@ -288,16 +322,17 @@ class WidgetProvider : AppWidgetProvider() {
context.getString( context.getString(
R.string.desc_album_cover, state.song.album.name.resolve(context))) R.string.desc_album_cover, state.song.album.name.resolve(context)))
} else { } else {
// We are unable to use the typical placeholder cover with the song item due to discardCover(context)
// limitations with the corner radius. Instead use a custom-made album icon as the
// placeholder.
setImageViewResource(R.id.widget_cover, R.drawable.ic_remote_default_cover_24)
setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover))
} }
return this return this
} }
private fun RemoteViews.discardCover(context: Context) {
setImageViewResource(R.id.widget_cover, R.drawable.ic_remote_default_cover_24)
setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover))
}
private fun RemoteViews.setupFillingCover(uiSettings: UISettings): RemoteViews { private fun RemoteViews.setupFillingCover(uiSettings: UISettings): RemoteViews {
// Below API 31, enable a rounded background only if round mode is enabled. // Below API 31, enable a rounded background only if round mode is enabled.
// On API 31+, the background should always be round in order to fit in with other // On API 31+, the background should always be round in order to fit in with other