Home / Changelog

What shipped in each release.

Notable user-facing changes and the engineering work behind them. Versions before v2.4.0 are catalogued in the project notes.

v3.6.5

2026-08-20

Fixed

  • Home no longer flashes the bundled US station on first launch. On a fresh install the top-stations shelf briefly rendered "NYE Underground" before the online directory refresh replaced it. The bundled row is now excluded from the top-stations queries entirely, so nothing appears there until real directory content is cached. NYE remains reachable for US users via Browse → USA and Search. (TASK-98)

v3.6.4

2026-08-18

Content screening, and a way to tell us. Stations from the online directory, playlist imports, custom stream URLs, backup restores and URL healing are screened as they enter the app, and the media player refuses to play a screened item however it got there. The watch app is screened on both its directory and its player. Third-party podcast feeds are screened on ingest. A new "Report content" action lets you flag a station or episode from Now Playing, or send a general report from Settings.

Added

  • Report content — an overflow action on Now Playing for the station or episode you are listening to, and a row in Settings for anything else. It opens a pre-filled email to RadioShake; if no email app is installed, the address is shown so it can be copied.

Changed

  • Stations are screened at every entry point and again at the player boundary, so a screened station cannot be played even if it was already saved on the device. Matching favourites, history entries and alarms are cleared on next launch, and are hidden from listings regardless.

Fixed

  • The bundled US station no longer appears in Home’s "Jump back in" card or "Recently played" shelf on devices where regional availability hides it elsewhere in the app.

v3.6.3

2026-08-18

A bundled station, and a stabler stream. NYE Underground — a community internet radio station broadcasting from Twentynine Palms, California — ships inside the app for the first time, establishing the mechanism that future curated stations will reuse. Alongside it: a long-standing playback edge case is fixed, and a browse-filter regression closes out.

Added

  • NYE Underground — a curated US station now ships inside the app and appears in Search and Browse for listeners outside the UK/EEA. Included with the operator’s permission.

Fixed

  • Stations imported from a .pls, .m3u or .m3u8 playlist can now recover via their playlist address when the direct stream fails; previously that fallback could never succeed.
  • Browsing by country now respects regional station availability even when a previously saved country is reopened.
  • A station that ships with the app no longer appears twice alongside its copy from the online directory.

v3.6.2

2026-07-30

A sleep timer you can actually hear. The headline is a fix for a fade that technically worked and practically didn’t. This release also clears the three Play Console technical-quality findings raised against v3.6.1 and takes 854 KB off the download.

Fixed

  • Sleep timer fade was inaudible (#267) — the fade stepped player.volume linearly from 1.0 to 0.0 over 30s, but gain-to-loudness is logarithmic, so nearly all the perceived quieting happened in the last couple of seconds and playback sounded like it simply cut off. It now follows an equal-dB curve (10^(-40 × progress / 20), fading to −40 dB before pausing), so the wind-down is gradual across the whole window. Reported by a Product Hunt user on the fall-asleep use case. Two related bugs surfaced while testing it and are fixed too: cancelling a fade mid-flight left player.volume stuck at a fraction, silencing the *next* playback, and a fade-duration change made while a timer was already counting down was silently ignored.
  • Content clipped under the status bar on Android 15+ (#264) — Android 15 enforces edge-to-edge, and screens that didn’t consume the system-bar insets rendered content underneath them.
  • Widget crash on Android 8-11 (#265) — AppWidgetProviderInfo.maxResizeWidth and maxResizeHeight are API 31+, and the widget’s sizing fallback read them unguarded, throwing NoSuchFieldError on older Android whenever the launcher hadn’t yet populated the widget options bundle.

Added

  • Configurable sleep-timer fade (#267) — choose 30 seconds, 1, 2 or 5 minutes in Settings. The default is now 1 minute, up from a fixed 30 seconds.

Changed

  • 854 KB smaller download (#266) — android.enableR8.fullMode and android.enableNewResourceShrinker.preciseShrinking are now set explicitly, and ~30 lines of -keep rules were removed after confirming they duplicated rules the libraries already ship as consumer rules. Release APK 7,846,740 → 6,992,106 bytes (10.9%).
  • Faster home-screen widget artwork (#265) — the widget was decoding full-resolution artwork (commonly 1024px+) into a surface capped at 400×300dp. It now does a two-pass decode with a computed inSampleSize, sized from the launcher’s actual widget dimensions. The download buffer is capped at 2 MB, because artwork URLs are user-contributed and a rogue server could otherwise exhaust the widget host’s memory.

v3.6.1

2026-07-22

Android 16 readiness. A maintenance release that updates RadioShake’s target platform to Android 16 (API 36) ahead of Google Play’s requirement, with a couple of fixes that came along for the ride.

Changed

  • Target Android 16 (API 36) (#260) — RadioShake now targets the latest Android platform so it stays updatable on Google Play (the store requires target API within one year of the latest release from 31 Aug 2026). Toolchain bumped to AGP 8.9.2 / Gradle 8.11.1.

Fixed

  • Quick Settings tile crash (#260) — tapping the play/pause tile with nothing playing no longer risks a crash on Android 14+; the tile now uses the supported PendingIntent launch path.

Added

  • Android Auto voice search (#260) — the media service now advertises MEDIA_PLAY_FROM_SEARCH, so "play " voice queries can start playback (the session already served search results).

v3.6.0

2026-07-15

A fresh interface, and radio in your car. RadioShake now supports Android Auto, and the phone app’s core screens have been redesigned around a cleaner information architecture.

Added

  • Android Auto (#231, #232, #233, #234) — browse and play RadioShake from the car. The browse root collapses to Android Auto’s tab budget (Home / Browse / Your Playlists / Podcasts), podcasts and completed downloads appear in the tree, stations carry clean display names, subtitles and branded artwork fallbacks, and each node hints the right list/grid content style. Playback resumption offers a one-tap card to resume your last station live, and the same resume backs the system media-resumption notification.

Changed

  • Now Playing redesign (#225) — cleaner metadata layout and a stronger control hierarchy.
  • Mini player polish (#226) — artwork, clearer status, no label overlap.
  • Home restructure (#227) — a resume hero, content shelves, and branded artwork fallbacks.
  • Information architecture (#241, #229) — a dedicated Library tab, a slimmer Settings screen, a clearer Search entry point, and consistent empty states across the app.
  • Browse polish (#243) — a dismissible region banner, unified station rows, and a more prominent View All.

Internal

  • On-device instrumentation test that verifies the Android Auto browse content-style contract over the real MediaBrowser IPC, wired into the nightly suite (#251).

v3.5.0

2026-07-13

Six languages, and a radio on your wrist. The whole interface is now translated, and the watch app no longer needs Bluetooth headphones.

Added

  • Full multi-language interface (#103) — every screen is now available in German, Spanish, French, Dutch, Brazilian Portuguese and European Portuguese (the picker shipped in v3.4; the remaining hardcoded strings — 71 keys + 2 plurals — are now externalised and translated). Every locale passed an on-device overflow/clipping pass on two form factors (Pixel 9 Pro XL native + Moto G7-sized 1080×2270 @ 400 dpi), 8 screens per locale, zero findings.
  • What’s Playing Now (#146, #213) — Discover gains a live song feed across your favourite stations: a one-shot ICY fetcher polls their StreamTitle without engaging the player, and detected songs surface as tappable rows with Shazam-derived artwork (including cross-station lookup), tap-to-play, and a proper empty state.
  • Watch speaker playback (#152) — the Wear app can route audio to the built-in watch speaker, no Bluetooth headphones needed. An output chip on the watch Now Playing screen switches between speaker and headphones, audio focus is handled per route, and a low-battery warning appears when playing through the speaker at ≤20%.
  • Watch favourites + sleep timer (#148) — toggle the current station as a favourite and set a sleep timer directly from the watch Now Playing screen.
  • Podcast Downloads screen — the Podcasts header gains a Downloads screen listing every download with per-row retry/delete and tap-to-play; the Settings storage-limit row shows live usage, and the download worker now respects the storage budget (and actually runs — a WorkManager/Hilt initialisation bug had been failing downloads silently).

Fixed

  • Separator-only ICY titles no longer render as "-" (#211) — degenerate StreamTitle values now fall back to the station name.
  • Podcasts section titles + "System default" language row translate correctly — the last two hardcoded-string gaps found during the #103 overflow pass.

Internal

  • Play Store listing text and graphics now ship from CI (:app:publishListing).
  • In-app review funnel telemetry.
  • Nightly E2E hardening: real XmlPullParser in unit tests (#193), deterministic DataStore scheduling in Turbine-backed ViewModel tests (#219), and the ~71-minute CI kill ceiling fixed (#236 — NAS runner job timeout raised from 1 h to 3 h).

v3.4.0

2026-06-26

Local audio files, smarter playlist imports, fewer crashes. Two new listening modes and a quieter, more reliable app underneath.

Added

  • Local audio file playback (#197) — Browse → Local files lets you play any MP3 / AAC / OGG / FLAC on the device. Two browse modes: pick from the system music library (requires READ_MEDIA_AUDIO) or open a specific folder via the SAF picker. Tracks play through the same MediaSession as radio and podcasts, with their own dedicated Now Playing screen (filename + folder + seek bar + 10s replay / 30s forward).
  • Playlist imports gain logos + progress UI (#207) — when you import stations via M3U/PLS/URL, the new logo enricher queries the radio-browser JSON catalogue per imported station and fills in missing artwork (the M3U format strips logos but the JSON catalogue carries them). A modal progress dialog shows live Saving 47/250 / Fetching logos 12/187 counts so big country-wide imports give feedback instead of sitting silent for 30 seconds. Failures are swallowed silently — a station without a logo just keeps the placeholder.

Fixed

  • No more launch-time OOM crashes on v3.3 — three Play Console Vitals reports flagged OutOfMemoryError on API 34 devices. Three mitigations: the blurred backdrop on the Now Playing / Podcast Show hero downsamples its source to 240px before the Modifier.blur(60.dp) GPU pass (~7.5 MB of bitmap RAM saved per screen open); Coil’s image memory cache capped at 12% of the heap (was 25%, fighting Media3 audio buffers and Room caches); playlist fetch capped at 10 MB so a misconfigured URL pointing at a binary can’t OOM the import.
  • Podcast Index 401/5xx no longer crashes the app at the splash screen. Wrapped the trending + episodes Flow onStart blocks in runCatching so a transient API failure leaves cached entries on screen rather than killing the view-model coroutine that loaded them.

Wear OS (separate release v1.0.8 — shipped 2026-06-23)

  • **androidx.wear.tiles 1.4.1 → 1.6.0** clears the Play Console SDK advisory about a SecurityException on Wear 5 (Android 14) when targeting API 35+. protolayout bumped 1.2.1 → 1.4.0 in tandem.

Other

  • The marketing site at radioshake.media gained unified schema (Organization publisher + canonical app @id + sameAs linking hiten.dev and the Play Store), /compare/ got FAQPage schema, nginx hardened with gzip + HSTS + CSP + a real 404 page, and the homepage/llms.txt now consistently describes the app as "no ads today" with an honest disclosure about a possible future tier.
  • New e2e.hiten-patel.co.uk dashboard collects Maestro run results across nightly runs; first project wired (RadioShake), more to come.

v3.1.1

2026-06-19

Languages, drag-to-reorder, consistent loudness, working Wear OS. A big quality-of-life release: seven app languages with an in-app picker, favourites you can reorder by long-press-and-drag, station loudness normalised so volume doesn’t jump between handovers, and Wear OS playback that actually works.

Added

  • Per-app language picker — Settings → Language. Seven languages bundled: English, Português (Portugal), Português (Brasil), Español, Nederlands, Français, Deutsch. Driven through Android 13+’s per-app locale API (AndroidManifest android:localeConfig + res/xml/locales_config.xml), falling back to AppCompat on older devices. Eight phases of R.string.* extraction across 30+ screens removed the hardcoded English literals that previously sat as un-translatable Kotlin strings.
  • Drag-to-reorder favourites (#186) — long-press a favourite tile on the Home → Favourites tab to grab it, drag to the new position. Order persists via a new sortOrder column on the favourites DAO. Sort picker now includes a "Custom" option that respects the user’s drag order; the existing "Date added / Alphabetical / Most played" sorts remain available.
  • Volume normalisation (#151, fixes #177) — stations now play at a consistent loudness so switching from a heavily-compressed pop station to a quiet jazz one no longer requires a manual volume bump. Settings → Volume normalisation lets you pick Off / Streaming (-14 LUFS) / Broadcast (-16 LUFS). Implemented via a Media3 LoudnessNormalisationProcessor audio sink.
  • Side-by-side debug + Play installs — the debug build now uses applicationIdSuffix = ".debug" so you can have the Play Store version and a dev build installed simultaneously without uninstall cycles when sideloading.

Fixed

  • Wear OS playback (#192, six-layer fix chain) — the v1.0.0–1.0.3 Wear OS releases couldn’t play stations at all. Root cause: the Media3 HLS module was missing from the watch module’s dependencies, so .m3u8 streams (Classic Vinyl HD, every BBC station, most modern radio) failed to resolve. Adding the HLS dependency surfaced four further problems (cleartext HTTP for non-HTTPS streams, MediaItem URI loss across the IPC boundary, missing wake lock, controller-binding race) — all fixed. Wear OS now plays stations as intended.
  • White-flash on language change — switching language in the picker no longer flashes the activity’s white window background. MainActivity now declares android:configChanges="locale|layoutDirection", so Android dispatches the locale change as onConfigurationChanged() and Compose recomposes in place instead of recreating the whole activity.
  • Wear app metadata — Wear OS listing now reaches Play Console (com.radioshake.wear cleared the publishing pipeline; release.yml’s publish-internal job handles the watch APK alongside the phone APK).

Infra (not user-facing)

  • Maestro E2E test suite (#204) — 30 flows total across 3 tiers (5 smoke + 5 features + 20 deep) covering cold launch, station playback, search, settings rows, Now Playing controls, sleep timer, insights, achievements, DNA, mood, custom stream dialog, alarm, stats, recordings, sleep sounds, playlists, song alerts, submit-a-station, parental controls, Song Log, Stations Like This, Backup + Restore SAF pickers, and search filter sheet. Nightly cron at 03:00 UTC fires on the developer’s Pixel 9 Pro XL via Tailscale ADB. Per-flow serial invocation with locale reset between flows. Media volume muted + restored, per-app locale saved + restored, runner ADB key persisted as repo secret to avoid USB-debug prompts at 3am.

v3.1.0

2026-06-15

Reliability + ask for ratings. A critical OOM crash fix that affected Android Auto, SystemUI media controls, and Wear OS browse, plus a polite in-app review prompt that fires after a few qualifying listening sessions.

Added

  • In-app review prompt — RadioShake now asks for a Play Store rating via Play’s ReviewManager once you have finished three listening sessions of at least 2 minutes, with a minimum app-install age of 3 days and a 90-day cooldown between prompts. The decision logic lives in ReviewPromptDecider and is covered by 10 unit-test cases. Goal: lift the rating off the baseline 3.0 (currently sitting on a single 5-star review) without nagging users mid-listen.

Fixed

  • **#201 — Parcel.readBundle OOM crash on browse**. RadioSessionCallback.onGetChildren and onGetSearchResult ignored the page / pageSize parameters that MediaLibraryService passes in, so every browse response tried to marshal the entire 45k-station catalogue into a single response Bundle. Receivers (Android Auto, SystemUI media controls, Wear OS browse) crashed inside Parcel.readBundle allocating the byte array. Now paginated via List.paged() with MAX_PAGE_SIZE = 100, falling back to DEFAULT_PAGE_SIZE = 50 for invalid or Integer.MAX_VALUE inputs, with a BROWSE_NODE_CAP = 200 backstop on favourites / search / playlist nodes. PagedTest covers the Integer.MAX_VALUE regression plus 8 other edge cases.

Infra

  • New .forgejo/workflows/pr-checks.yml runs :app:testDebugUnitTest on every PR and non-main push. Mirrors the JDK 17 + Android SDK 35 + qemu-user-static aapt2-on-arm64 setup from release.yml. Gates ReviewPromptDeciderTest, PagedTest, and all future unit tests.
  • scripts/play-analytics-snapshot.py extended with five new CSVs (country, app version, store performance, traffic source, funnel summary). Unlocked the first real store-listing conversion-rate visibility: ~18.6% in May (50 / 269 visitors) — confirming the listing converts at industry baseline and that traffic volume, not copy or screenshots, is the main growth bottleneck.

v3.0.0

2026-06-07

Feature drop — three user-facing additions, two quality fixes, and the first version-controlled Play Store listing.

Added

  • Mood combos on discovery. Pick up to three moods (e.g. Chill + Late-night). Stations matching more selected moods rank higher; flip the new Strict toggle to filter to stations matching every mood. The last combo persists as a Quick-pick chip.
  • M3U / PLS playlist import. Bulk-import station collections from a playlist file via Settings → Import M3U/PLS. Lenient parser, system file picker, automatic dedupe against favourites, snackbar summary.
  • Wear OS Tile. A watch-face Tile showing the current station, the live ICY artist/track, and a one-tap play/pause. Updates within seconds of state changes. Brings Wear OS reach to competitive parity with Spotify and Pocket Casts.

Fixed

  • Cast button now works. The Cast button on Now Playing opens the route-chooser dialog instead of doing nothing. Root cause was a missing FragmentManager host.
  • Android 15 / SDK 35 edge-to-edge. Cleared the Play Console advisory. The Shake overlay's Close button now uses safeDrawingPadding() so it clears the status bar on gesture-nav devices.
  • Shake-to-discover false positives. No longer fires on incidental bumps (phone set on desk, pocket transitions). New pattern matcher requires three+ acceleration spikes within 500 ms before triggering.

Marketing

  • First version-controlled Play Store listing copy and screenshots, plus a hand-coded SVG feature graphic.

v2.4.2

2026-05-27

Hotfix — v2.4.1 didn’t actually fix the station-open crash; this does.

Fixed

  • Opening any station crashed — turned out to be two bugs in the Cast button, both on the Now Playing path. MediaRouteButton's constructor relies on AppCompat's colorPrimary, which a pure-Material Android theme never defines, so the contrast check threw IllegalArgumentException. The previous fix used a ThemeOverlay that didn't supply colorPrimary. Real fix: a dedicated cast-button theme that sets an opaque colorPrimary, plus tinting the indicator drawable directly instead of casting MediaRouteButton to ImageView (it extends View).

v2.4.1

2026-05-25

Hotfix — crash on opening any station under certain OEM theme overlays (later confirmed incomplete; see v2.4.2).

Fixed

  • Theme-overlay crash on the Cast button under edge-to-edge on some OEM themes (observed: OnePlus 8 Pro / Android 11). Crashlytics: 11 events / 3 users in 24h on v2.4.0.

v2.4.0

2026-05-14

First release shipped via the autonomous Gradle Play Publisher pipeline. A single git tag v2.4.0; git push --tags produced a signed AAB, uploaded to Play Console internal track, then a workflow promoted to production at 100%.

Added

  • EPG programme guide coverage for Spain, Sweden, Norway, Canada and Australia (5 new feeds).
  • Crashlytics coroutine scope helper — uncaught throwables in long-lived coroutines now go to FirebaseCrashlytics.recordException instead of the JVM uncaught handler.

Fixed

  • SQLite UPSERT crash on pre-Android-11 devices — replaced UPSERT with a transactional INSERT OR IGNORE + UPDATE. Confirmed by a Crashlytics report from a Moto G7 Power user.
  • Onboarding bottom action buttons sat behind the Android 15 nav bar on gesture-nav devices. Added safeDrawingPadding().
  • AudioTrack write-on-released race in AmbientSoundPlayer.

Earlier versions (v1.0.0 — v2.3.0) are catalogued in internal release notes. Significant earlier milestones include initial Play Store launch, Android Auto support, Chromecast Cast wiring, and the Wear OS standalone build.