<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Mediafloat on ICE-ICE-BEAR-BLOG</title><link>https://ice-ice-bear.github.io/tags/mediafloat/</link><description>Recent content in Mediafloat on ICE-ICE-BEAR-BLOG</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Fri, 03 Apr 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://ice-ice-bear.github.io/tags/mediafloat/index.xml" rel="self" type="application/rss+xml"/><item><title>MediaFloat: Anatomy of an Android Floating Media Control Overlay</title><link>https://ice-ice-bear.github.io/posts/2026-04-03-mediafloat-android/</link><pubDate>Fri, 03 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/posts/2026-04-03-mediafloat-android/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post MediaFloat: Anatomy of an Android Floating Media Control Overlay" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;Reaching for media controls on Android typically means pulling down the notification shade or switching apps entirely. &lt;strong&gt;MediaFloat&lt;/strong&gt; takes a different approach: a compact, draggable overlay bar showing Previous, Play/Pause, and Next stays visible above every app, always within reach.&lt;/p&gt;
&lt;p&gt;Built in Kotlin with Jetpack Compose, targeting Android 10+, and released under Apache License 2.0, MediaFloat is a focused single-purpose tool. The source lives at &lt;a class="link" href="https://github.com/Leuconoe/MediaFloat" target="_blank" rel="noopener"
 &gt;Leuconoe/MediaFloat&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="core-architecture"&gt;Core Architecture
&lt;/h2&gt;&lt;p&gt;MediaFloat combines three Android system capabilities to deliver its persistent overlay:&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 A["Media App&amp;lt;br/&amp;gt;(YouTube, Spotify, etc.)"] --&gt;|"Publishes MediaSession"| B["NotificationListenerService&amp;lt;br/&amp;gt;(Detects active media sessions)"]
 B --&gt;|"Playback state &amp;amp; transport actions"| C["ForegroundService&amp;lt;br/&amp;gt;(Overlay runtime)"]
 C --&gt;|"WindowManager overlay"| D["Compose UI&amp;lt;br/&amp;gt;(Floating control bar)"]
 D --&gt;|"Previous / Play / Next"| B
 E["User Settings&amp;lt;br/&amp;gt;(Main / Settings / Advanced)"] --&gt;|"Position, size, theme"| C&lt;/pre&gt;&lt;h3 id="the-three-permission-pillars"&gt;The Three Permission Pillars
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Permission or Access&lt;/th&gt;
 &lt;th&gt;Role&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;SYSTEM_ALERT_WINDOW&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Draws the floating bar above all other apps&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;FOREGROUND_SERVICE&lt;/code&gt; + &lt;code&gt;FOREGROUND_SERVICE_SPECIAL_USE&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Keeps the overlay runtime alive in the background&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;POST_NOTIFICATIONS&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Required foreground-service notification (Android 13+)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Notification listener access&lt;/td&gt;
 &lt;td&gt;Reads active &lt;code&gt;MediaSession&lt;/code&gt; state and transport actions&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="android-overlay-how-it-works"&gt;Android Overlay: How It Works
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;SYSTEM_ALERT_WINDOW&lt;/code&gt; — labeled &amp;ldquo;Display over other apps&amp;rdquo; in Android settings — lets an app insert views into the system window layer via &lt;code&gt;WindowManager.addView()&lt;/code&gt;. This sits above the normal app window hierarchy, which is why the overlay remains visible regardless of what the user is doing.&lt;/p&gt;
&lt;p&gt;MediaFloat pairs this with &lt;strong&gt;Jetpack Compose&lt;/strong&gt;. Rather than inflating XML layouts into the overlay window, a &lt;code&gt;ComposeView&lt;/code&gt; is embedded into the &lt;code&gt;WindowManager&lt;/code&gt;-managed surface. This gives the floating bar the full expressive power of Material 3 Compose components while keeping it lightweight.&lt;/p&gt;
&lt;h3 id="why-a-foreground-service-is-non-negotiable"&gt;Why a Foreground Service Is Non-Negotiable
&lt;/h3&gt;&lt;p&gt;Android aggressively kills background processes to preserve battery. Any UI component that must persist when the host app is backgrounded needs to run inside a &lt;strong&gt;Foreground Service&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The service must post a user-visible notification — the cost of keeping the overlay alive&lt;/li&gt;
&lt;li&gt;Android 13+ requires &lt;code&gt;POST_NOTIFICATIONS&lt;/code&gt; to show that notification&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;FOREGROUND_SERVICE_SPECIAL_USE&lt;/code&gt; type specifically covers non-standard foreground service use cases like screen overlays&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="notificationlistenerservice-the-media-session-bridge"&gt;NotificationListenerService: The Media Session Bridge
&lt;/h3&gt;&lt;p&gt;Media apps publish playback state through Android&amp;rsquo;s &lt;code&gt;MediaSession&lt;/code&gt; API. &lt;code&gt;NotificationListenerService&lt;/code&gt; gives MediaFloat a system-level subscription to those sessions. Once a session is detected, &lt;code&gt;MediaController&lt;/code&gt; handles the transport commands — Previous, Play/Pause, Next — dispatched back to whatever media app is active.&lt;/p&gt;
&lt;p&gt;This architecture means MediaFloat works identically with Spotify, YouTube, podcast apps, or any app that exposes a &lt;code&gt;MediaSession&lt;/code&gt;. No app-specific integration required.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="app-structure-single-module-five-surfaces"&gt;App Structure: Single Module, Five Surfaces
&lt;/h2&gt;&lt;p&gt;MediaFloat deliberately stays as a single-module Android app. The README explicitly calls this out as a way to keep setup, runtime behavior, and recovery paths understandable.&lt;/p&gt;
&lt;h3 id="the-five-app-surfaces"&gt;The Five App Surfaces
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 Main["Main&amp;lt;br/&amp;gt;Start / stop overlay&amp;lt;br/&amp;gt;Readiness check"]
 Settings["Settings&amp;lt;br/&amp;gt;Buttons, size presets&amp;lt;br/&amp;gt;Opacity, behavior"]
 Advanced["Advanced&amp;lt;br/&amp;gt;Language, theme&amp;lt;br/&amp;gt;Sidebar, persistent mode"]
 Support["Support&amp;lt;br/&amp;gt;Setup guidance&amp;lt;br/&amp;gt;Version, license"]
 Debug["Debug&amp;lt;br/&amp;gt;Runtime diagnostics&amp;lt;br/&amp;gt;Transport commands"]

 Main --- Settings
 Settings --- Advanced
 Advanced --- Support
 Support --- Debug&lt;/pre&gt;&lt;p&gt;The &lt;strong&gt;Debug&lt;/strong&gt; surface stands out: it exposes runtime readiness inspection, media session diagnostics, direct transport command sending, log clearing, and a recent events view. Shipping developer tooling inside the release build — behind an Advanced setting toggle — is a practical pattern for overlay apps where permission state and service lifecycle are inherently hard to observe from the outside.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="readiness-checks-and-fault-recovery"&gt;Readiness Checks and Fault Recovery
&lt;/h2&gt;&lt;p&gt;MediaFloat models its startup preconditions explicitly. Before the overlay can run, three conditions must hold:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Overlay access granted (&lt;code&gt;SYSTEM_ALERT_WINDOW&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Notification listener access granted&lt;/li&gt;
&lt;li&gt;Notification posting permitted (Android 13+)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If any condition is missing, the app surfaces shortcuts directly to the relevant Android settings screen rather than showing a generic error. This is the kind of detail that separates a polished overlay app from a frustrating one — Android&amp;rsquo;s permission model is multi-step, and guiding the user through each gate matters.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="automation-integration"&gt;Automation Integration
&lt;/h2&gt;&lt;p&gt;MediaFloat exposes an exported intent action:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sw2.io.mediafloat.action.SHOW_OVERLAY
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This lets external automation tools — Tasker, MacroDroid, Android Shortcuts, Bixby Routines — trigger the overlay flow without opening the app UI. The launcher shortcut set also exposes both &lt;code&gt;Launch widget&lt;/code&gt; and &lt;code&gt;Stop widget&lt;/code&gt; as pinnable home-screen shortcuts via &lt;code&gt;ShortcutManager&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If the readiness preconditions are not met when the action fires, the app falls back to the main UI so the user can complete setup.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="multi-language-support"&gt;Multi-Language Support
&lt;/h2&gt;&lt;p&gt;v0.2.1 uses the &lt;code&gt;AppCompat&lt;/code&gt; app-language API, which provides per-app locale selection on Android 13+ and graceful fallback on older supported versions. Shipped languages: System default, English, Korean, Chinese, Japanese, Spanish, and French.&lt;/p&gt;
&lt;p&gt;The language picker lives in &lt;strong&gt;Advanced&lt;/strong&gt;; the current active language is reflected in &lt;strong&gt;Support&lt;/strong&gt;. This is the correct pattern for in-app language switching without requiring a system-level locale change.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-v021-intentionally-omits"&gt;What v0.2.1 Intentionally Omits
&lt;/h2&gt;&lt;p&gt;The README is upfront about current constraints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No freeform resizing — only built-in size presets&lt;/li&gt;
&lt;li&gt;Single horizontal control family — no alternative button arrangements&lt;/li&gt;
&lt;li&gt;Button combinations limited to Previous / Play·Pause / Next layouts&lt;/li&gt;
&lt;li&gt;Overlay behavior depends on Android permission state and an active &lt;code&gt;MediaSession&lt;/code&gt; being available&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;ldquo;Intentionally constrained&amp;rdquo; is the phrase used, reflecting a design philosophy that prioritizes stability and comprehensibility over feature breadth. Recent commits point toward v0.3.0 with thumbnail support and sidebar spacing refinements already merged.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="tech-stack-summary"&gt;Tech Stack Summary
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Item&lt;/th&gt;
 &lt;th&gt;Detail&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Language&lt;/td&gt;
 &lt;td&gt;Kotlin&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UI Framework&lt;/td&gt;
 &lt;td&gt;Jetpack Compose + Material 3&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Target Platform&lt;/td&gt;
 &lt;td&gt;Android 10+&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Build System&lt;/td&gt;
 &lt;td&gt;Gradle&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;License&lt;/td&gt;
 &lt;td&gt;Apache License 2.0&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Key Android APIs&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;SYSTEM_ALERT_WINDOW&lt;/code&gt;, &lt;code&gt;ForegroundService&lt;/code&gt;, &lt;code&gt;NotificationListenerService&lt;/code&gt;, &lt;code&gt;MediaController&lt;/code&gt;, &lt;code&gt;ShortcutManager&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="takeaways"&gt;Takeaways
&lt;/h2&gt;&lt;p&gt;MediaFloat is a clean reference implementation for the Android floating overlay pattern. The combination of &lt;code&gt;SYSTEM_ALERT_WINDOW&lt;/code&gt; + Foreground Service + &lt;code&gt;NotificationListenerService&lt;/code&gt; is the standard three-part recipe for any persistent, system-level UI that needs to respond to media state — and MediaFloat keeps each piece clearly separated.&lt;/p&gt;
&lt;p&gt;A few implementation choices worth noting for anyone building similar apps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using Jetpack Compose inside a &lt;code&gt;WindowManager&lt;/code&gt; overlay surface is increasingly the right default over XML-inflated views&lt;/li&gt;
&lt;li&gt;The exported automation action (&lt;code&gt;SHOW_OVERLAY&lt;/code&gt;) is a low-cost way to make a utility app composable in user workflows&lt;/li&gt;
&lt;li&gt;Shipping Debug tooling inside the app — gated behind an Advanced toggle — is the right call for anything involving Android permissions and service lifecycle, where external observability is limited&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Build it yourself with &lt;code&gt;./gradlew installDebug&lt;/code&gt; after cloning the repository. Release signing is documented in &lt;code&gt;keystore.properties.example&lt;/code&gt;.&lt;/p&gt;</description></item></channel></rss>