Table of Contents

    Book an Appointment

    What Was the Origin of Our Android Floating Window Challenge?

    While working on a comprehensive field service management platform for the logistics industry, we encountered a situation where workflow continuity was being severely disrupted. Field technicians were using a dedicated Android application built in Kotlin and Jetpack Compose to manage dispatch checklists, active timers and live location tracking. However, they frequently needed to reference external vendor manuals or web portals to verify part numbers during a job.

    Whenever a technician tapped a link, the OS would launch the default browser, pushing our primary application into the background. On lower-end rugged devices, this context switch often caused the OS to aggressively kill our app to free up memory, resulting in lost checklist states and inaccurate time logs. We realized we needed a way to open a browser page or launch an external activity without forcing the user to leave the active full-screen application.

    This challenge led us down the complex path of Android window management. We needed to implement an android floating window that could render external content while keeping our primary Compose app active. Because OEM fragmentation makes window management notoriously tricky, this experience inspired this article so other engineering teams can avoid the common pitfalls we navigated. When enterprise companies need to solve deep OS-level challenges, they often look to hire software developer teams with proven hardware and OS integration experience.

    Why Was a Floating UI Crucial for This Business Use Case?

    In high-stakes operational environments, context switching is a liability. The business use case demanded that the primary task screen—displaying a live job timer and safety checklist—remain visible at all times. If a technician needed to look up a parts manual, that manual had to appear over the existing interface, allowing the user to read the specifications while simultaneously checking off items on the primary screen.

    Architecturally, this meant we could not rely on standard Intent routing. A standard Intent.ACTION_VIEW forces a task stack change. We needed a UI overlay that could act as an independent interaction layer. The requirement was specifically to render a browser page or a separate activity in a resizable, draggable window that hovered above the primary Jetpack Compose activity, effectively bypassing the standard Android lifecycle behavior of backgrounding the caller app.

    What Went Wrong When Attempting Native Multi-Window Solutions?

    Our initial assumption was that Android’s native multi-window APIs would handle this gracefully. We attempted to use freeform window mode to launch the external browser activity.

    The symptoms of our failure surfaced immediately during device testing. While our code perfectly launched a floating browser window on stock Android emulators and certain tablets, the behavior was wildly unpredictable on physical devices. On heavily customized OEM skins, the Intent either ignored our window bounds and launched full screen or it crashed the launcher. The system logs revealed that freeform window mode was often disabled at the OS level by device manufacturers unless explicitly enabled in Developer Options.

    This architectural oversight taught us a harsh lesson: relying on the target activity (like Chrome) to respect freeform window bounds is a fragile strategy across a fragmented hardware fleet. We could not control how third-party apps responded to windowing flags.

    How Did We Evaluate Approaches to Launching Overlay Apps?

    Realizing that native freeform intents were too unstable for production, we evaluated several alternative strategies to achieve our android floating window. When companies hire android developers for enterprise mobility, evaluating these types of architectural tradeoffs is precisely the expertise they expect.

    Can We Use the Picture-in-Picture (PiP) API?

    We considered using the native Picture-in-Picture mode. While PiP allows an activity to float over other apps, it is strictly designed for media consumption (like video playback or navigation). The PiP window heavily restricts touch interactions, meaning users could not scroll a web page or tap links inside the manual. This immediately disqualified PiP for our interactive browser requirement.

    What About Freeform Window Mode Using ActivityOptions?

    We revisited launching a distinct Activity using ActivityOptions.setLaunchBounds() combined with Intent.FLAG_ACTIVITY_NEW_TASK and Intent.FLAG_ACTIVITY_MULTIPLE_TASK. If the OS supports it, this launches the target app in a floating window. However, because we needed guaranteed execution across locked-down rugged devices, the unreliability of OEM support meant this could only serve as a fallback, not a primary solution.

    Is SYSTEM_ALERT_WINDOW the Most Reliable Approach?

    We ultimately determined that the most robust way to simulate launching another app (like a browser) as a floating window without leaving our app was to build a custom overlay using the WindowManager API, backed by a Foreground Service. By requesting the SYSTEM_ALERT_WINDOW permission, we could draw a draggable window containing a WebView. This allowed us to keep the user inside our application’s ecosystem, perfectly render the external web content and ensure the primary Compose activity remained in the active foreground state.

    How Did We Implement the Android Floating Window in Compose?

    To implement this in a modern Kotlin and Jetpack Compose architecture, we created a Foreground Service that interacts with the WindowManager. Injecting a Compose View into the WindowManager requires manually setting up the lifecycle owners, which is typically handled automatically by an Activity.

    Step 1: The WindowManager Service Configuration

    We configured a service to inflate a custom View that hosts our Compose UI. We ensured we requested the Settings.ACTION_MANAGE_OVERLAY_PERMISSION from the user before launching this service.

    class FloatingBrowserService : Service() {
        private lateinit var windowManager: WindowManager
        private lateinit var floatingView: ComposeView
        override fun onCreate() {
            super.onCreate()
            windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
            
            val layoutParams = WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 
                else 
                    WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or 
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                PixelFormat.TRANSLUCENT
            )
            layoutParams.gravity = Gravity.TOP or Gravity.START
            layoutParams.x = 100
            layoutParams.y = 100
            floatingView = ComposeView(this).apply {
                setViewTreeLifecycleOwner(MyLifecycleOwner())
                setViewTreeSavedStateRegistryOwner(MySavedStateOwner())
                setViewTreeViewModelStoreOwner(MyViewModelStoreOwner())
                setContent {
                    FloatingBrowserComponent(url = "https://generic-vendor-manuals.com/parts")
                }
            }
            windowManager.addView(floatingView, layoutParams)
            setupDragListener(floatingView, layoutParams)
        }
        override fun onBind(intent: Intent?): IBinder? = null
        
        override fun onDestroy() {
            super.onDestroy()
            if (::floatingView.isInitialized) {
                windowManager.removeView(floatingView)
            }
        }
    }
    

    Step 2: Implementing the Compose WebView

    Inside the Compose component, we utilized a standard AndroidView to host the WebView. This isolated the browser logic entirely within the floating window.

    @Composable
    fun FloatingBrowserComponent(url: String) {
        Surface(
            modifier = Modifier.size(300.dp, 400.dp),
            shape = RoundedCornerShape(8.dp),
            elevation = 8.dp
        ) {
            Column {
                // Drag Handle UI
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(30.dp)
                        .background(Color.DarkGray)
                )
                // Browser content
                AndroidView(factory = { context ->
                    WebView(context).apply {
                        settings.javaScriptEnabled = true
                        webViewClient = WebViewClient()
                        loadUrl(url)
                    }
                })
            }
        }
    }
    

    Step 3: Validation and Performance Considerations

    By coupling this with a Foreground Service, we guaranteed that the OS would not kill the application while the technician was reading the manual. We validated the solution by monitoring memory heaps; because we were hosting the WebView inside our own process rather than launching an external Chrome intent, memory consumption was predictable. Security was handled by strictly locking down the WebView settings, disabling file access and ensuring only HTTPS URLs from verified vendor domains could be loaded.

    What Key Lessons Can Engineering Teams Apply?

    Solving this UI paradigm yielded several architectural lessons that are valuable whether you are maintaining an internal tool or planning to hire app developer to create a mobile app from scratch.

    • Do Not Rely on OEM Windowing Behaviors: Standard multi-window and freeform APIs are highly fragmented. Always build custom overlays if absolute consistency is required across different hardware manufacturers.
    • Manage Compose Lifecycles Manually in Services: Jetpack Compose relies heavily on ViewTree owners (Lifecycle, ViewModel, SavedState). When injecting Compose directly into a WindowManager, you must implement and attach these owners manually or the Compose code will crash.
    • Foreground Services Are Essential for Overlays: An overlay running from a background service will likely be killed by Android’s battery optimization. Always tie your android floating window to a Foreground Service with a persistent notification.
    • Touch Event Routing Requires Care: Use FLAG_NOT_TOUCH_MODAL to ensure that touches outside your floating window are passed down to the underlying full-screen application.
    • Prioritize Security in WebViews: When simulating an external browser inside your app’s process, ensure JavaScript interfaces are restricted and DOM storage is cleared between sessions to prevent cross-site scripting vulnerabilities.

    How Can We Wrap Up This Architectural Journey?

    Balancing deep OS integrations with modern UI frameworks like Jetpack Compose requires a thorough understanding of Android’s system architecture. By moving away from unreliable freeform intents and engineering a custom WindowManager solution, we delivered a resilient android floating window that empowered field technicians to multitask without losing their workflow state. Building stable, edge-case-resistant enterprise software requires experienced engineering teams who understand how to navigate OEM fragmentation. If your organization is facing similar complex architectural challenges and needs dedicated technical expertise, contact us.

    Social Hashtags

    #AndroidDevelopment #JetpackCompose #Kotlin #AndroidFloatingWindow #WindowManager #AndroidUI #AndroidDevelopers #MobileAppDevelopment #KotlinAndroid #AndroidTutorial #WebView #SoftwareEngineering #ComposeUI #AndroidOverlay #EnterpriseMobility

    Frequently Asked Questions

    Success Stories That Inspire

    See how our team takes complex business challenges and turns them into powerful, scalable digital solutions. From custom software and web applications to automation, integrations, and cloud-ready systems, each project reflects our commitment to innovation, performance, and long-term value.