4.0 KiB · text History 86fa652

Media Bubbles (Android) — org.neiam.mediabubbles

A persistent, draggable media-control bubble drawn over other apps (e.g. a map during navigation). Recreates the deprecated “media controls in navigation” feature without depending on any particular player or map app.

Reads playback through the system MediaSessionManager, using the same notification-listener mechanism as ../scropple-android. Kotlin · Jetpack Compose.

Build & run

export JAVA_HOME=~/.gradle/jdks/eclipse_adoptium-21-amd64-linux.2   # JDK 17–21; JDK 25 (system + Android Studio JBR) breaks lint
./gradlew :app:assembleDebug
./gradlew :app:installDebug

First-run setup

  1. Media access → enable the app under Notification access (unlocks media sessions; no notification content is read).
  2. Display over other apps → allow.
  3. (Android 13+, optional) Notifications → lets the silent “bubble active / Hide” notification appear.
  4. Flip Show bubble, or add the Media bubble Quick Settings tile.

Using the bubble

Gesture Action
Tap Expand / collapse controls
Double-tap (collapsed, optional) Play / pause
Drag Move; snaps to the nearest left/right edge, position remembered
Long-press Open the playing app
Drag to the bottom target Dismiss (stays off until re-enabled; can be turned off in settings)

Collapsed, the bubble shows album art with a progress ring and a pause glyph when paused. Expanded, it grows inward from the docked edge and shows title/artist plus prev / ±10 s / play-pause / next. Touches outside the bubble pass straight through to the app underneath.

Configuration

Size (40–120 dp), opacity, auto-collapse delay, and toggles for title, prev/next, seek ±10 s, the progress ring, double-tap play/pause, and hiding when no session exists. Settings apply live, and the settings screen includes a working preview.

Look & feel

Shared with the other neiam Android apps (bigtrippr, fieri-eternal), with bigtrippr as the reference copy:

  • ui/theme/AppTheme.kt: the daisyUI palettes (Light, Dark, Her, After Dark, Forest, Sky, Clays, Stones, Blueprint) defined in OKLCh, with every Material 3 slot pinned so Material’s baseline purple never shows. System follows the OS between Light and Dark.
  • Typography.kt: B612 throughout. Shapes.kt: the shared corner radii.
  • ThemePicker.kt: the web picker’s list with swatches. Common.kt: an AppBarTitle in the accent color, B612 Bold.
  • System bars are painted in the palette’s base color, and the window starts on After Dark so launch doesn’t flash white.

The bubble follows the chosen palette too: base-100 body, primary play button and progress ring, base-content text.

Architecture

Package Role
media FloatyListenerService (notification listener; also restores the bubble after boot), MediaSessionWatcher (picks the most relevant session), MediaRepository (state + transport controls)
overlay OverlayService (foreground specialUse service that owns the TYPE_APPLICATION_OVERLAY window), DragFrameLayout (raw-coordinate drag), MediaBubble (Compose UI)
data BubblePrefs — config + position as StateFlows
tile BubbleTileService — Quick Settings toggle
ui Settings screen, theme, permission helpers

Session selection: the session that most recently started playing, else the most recently active one, else the system’s top-priority session.

Known gaps

  • Not yet verified on a physical device.
  • No manual source picker or per-app filter when several players are active.
  • No auto-show tied to a specific app being in the foreground (would need Usage Access).

CI

.forgejo/workflows/android.yml + Dockerfile, ported from Scropple: on push to master, builds the release APK in a container, signs it with the ANDROID_KEYSTORE / ANDROID_KEYSTORE_PASS repo secrets (skips cleanly if unset), and publishes it to the forge’s F-Droid repository. Use a new keystore for this app; the signing cert can never change without forcing reinstalls.