diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt index d0776e4a6..3972718e8 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt @@ -285,8 +285,9 @@ private class MediaStoreExtractorImpl( // 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 // replicate the majority behavior described prior. - rawSong.albumName = cursor.getStringOrNull(albumIndex) - ?: requireNotNull(rawSong.path?.name) { "Invalid raw: No path" } + rawSong.albumName = + 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 // as , 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 diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt index 2ee0fcafd..80b8758e8 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt @@ -101,17 +101,51 @@ class WidgetProvider : AppWidgetProvider() { SizeF(180f, 272f) to newThinPaneLayout(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. val awm = AppWidgetManager.getInstance(context) val component = ComponentName(context, this::class.java) - try { - awm.updateAppWidgetCompat(context, component, views) - logD("Successfully updated RemoteViews layout") - } catch (e: Exception) { - // Layout update failed, gracefully degrade to the default widget. - logW("Unable to update widget: $e") - reset(context, uiSettings) + while (victims.size > 0) { + try { + awm.updateAppWidgetCompat(context, component, views) + logD("Successfully updated RemoteViews layout") + return + } catch (e: IllegalArgumentException) { + 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( R.string.desc_album_cover, state.song.album.name.resolve(context))) } else { - // We are unable to use the typical placeholder cover with the song item due to - // 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)) + discardCover(context) } 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 { // 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