/** The app's own icon, decoded ONCE and kept, as the card's large icon * when there is no cover yet. `art` is cleared the moment `artKey` * changes and only refilled when the frontend has finished decoding a * cover into bytes — and a track with no cover never refills it at all — * so every track change has a real window with a null large icon and the * lock screen shows a grey hole. This is what stands in it. * * Through the PackageManager rather than a resource id on purpose: this * is a library module and its own R has no `ic_launcher`, so from in here * the app's icon is only reachable this way. * * Drawn at the tile's own bounds, which is the whole trick. On API 26+ * what comes back is an AdaptiveIconDrawable, and an adaptive icon's * BOUNDS are the 72 units the launcher shows — not the 108 its layers * are drawn in. The drawable scales its own layers up by 3/2 and masks * them inside whatever box it is handed, which is why its intrinsic * width is two thirds of the layer PNG. So setBounds(0, 0, 512, 512) IS * the launcher's view; a box set outside the tile to "reveal the 18% * bleed" does the opposite and crops into the safe zone. Every other * kind of drawable simply fills the tile. * * Decoded on the first ask and never again — `refresh()` runs on every * position tick, and this must not. A failure caches nothing and answers * null, which is the card exactly as it was before this existed. */ fun appIcon(ctx: Context): Bitmap? { val cached = icon if (cached != null) return cached return try { val drawable = ctx.packageManager.getApplicationIcon(ctx.packageName) val bitmap = Bitmap.createBitmap(512, 512, Bitmap.Config.ARGB_8888) drawable.setBounds(0, 0, 512, 512) drawable.draw(Canvas(bitmap)) icon = bitmap bitmap } catch (e: Exception) { android.util.Log.w("SoulStream", "app icon could not be decoded: $e") null } }