Skip to content

Instantly share code, notes, and snippets.

@handstandsam
Last active July 13, 2024 05:35
Show Gist options
  • Save handstandsam/6ecff2f39da72c0b38c07aa80bbb5a2f to your computer and use it in GitHub Desktop.
Save handstandsam/6ecff2f39da72c0b38c07aa80bbb5a2f to your computer and use it in GitHub Desktop.
Jetpack Compose OverlayService. You have to have all the correct permissions granted and in your manifest, but if you do, this this will show a green box with "Hello" in it!
import android.os.Bundle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleRegistry
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
internal class MyLifecycleOwner : SavedStateRegistryOwner {
private var mLifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
private var mSavedStateRegistryController: SavedStateRegistryController = SavedStateRegistryController.create(this)
/**
* @return True if the Lifecycle has been initialized.
*/
val isInitialized: Boolean
get() = true
override fun getLifecycle(): Lifecycle {
return mLifecycleRegistry
}
fun setCurrentState(state: Lifecycle.State) {
mLifecycleRegistry.currentState = state
}
fun handleLifecycleEvent(event: Lifecycle.Event) {
mLifecycleRegistry.handleLifecycleEvent(event)
}
override fun getSavedStateRegistry(): SavedStateRegistry {
return mSavedStateRegistryController.savedStateRegistry
}
fun performRestore(savedState: Bundle?) {
mSavedStateRegistryController.performRestore(savedState)
}
fun performSave(outBundle: Bundle) {
mSavedStateRegistryController.performSave(outBundle)
}
}
import android.app.AlertDialog
import android.app.Service
import android.content.Intent
import android.graphics.PixelFormat
import android.os.Build
import android.os.IBinder
import android.util.TypedValue
import android.view.Window
import android.view.WindowManager
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
import androidx.savedstate.ViewTreeSavedStateRegistryOwner
import com.viatek.fitnation.echelon_android.R
class OverlayService : Service() {
val windowManager get() = getSystemService(WINDOW_SERVICE) as WindowManager
override fun onCreate() {
super.onCreate()
setTheme(R.style.ThemeOverlay_AppCompat_Light)
showOverlay()
}
private fun showOverlay() {
val layoutFlag: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
layoutFlag,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
val composeView = ComposeView(this)
composeView.setContent {
Text(
text = "Hello",
color = Color.Black,
fontSize = 50.sp,
modifier = Modifier
.wrapContentSize()
.background(Color.Green)
)
}
// Trick The ComposeView into thinking we are tracking lifecycle
val viewModelStore = ViewModelStore()
val lifecycleOwner = MyLifecycleOwner()
lifecycleOwner.performRestore(null)
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
ViewTreeLifecycleOwner.set(composeView, lifecycleOwner)
ViewTreeViewModelStoreOwner.set(composeView) { viewModelStore }
ViewTreeSavedStateRegistryOwner.set(composeView, lifecycleOwner)
windowManager.addView(composeView, params)
}
override fun onBind(intent: Intent): IBinder? {
return null
}
}
@farhanlaskani
Copy link

farhanlaskani commented Mar 30, 2024

give permission in manifest file::

permission android:name="android.permission.SYSTEM_ALERT_WINDOW"

in MainActivity code is ::

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (!Settings.canDrawOverlays(this)) {
        val intent = Intent(
            Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
            Uri.parse("package:$packageName")
        )
        startActivityForResult(intent, 101)
    }
    setContent {

        TikDownloaderTheme {
            // A surface container using the 'background' color from the theme
            Surface(
                modifier = Modifier.fillMaxSize(),
                color = MaterialTheme.colorScheme.background
            ) {
                val context = LocalContext.current
                val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
                AddFloatingButtonOverlay(windowManager)

            }
        }
    }
}

}

@composable
fun AddFloatingButtonOverlay(windowManager: WindowManager) {
val context = LocalContext.current
val composeView = remember {
ComposeView(context).apply {
setContent {
Box {
FloatingButton(onClick = { })
}
}
}
}

// Set layout parameters for the floating button
val params = WindowManager.LayoutParams(
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
    android.graphics.PixelFormat.TRANSLUCENT
)

// Set the position of the floating button on the screen
params.x = 100 // Set the X position
params.y = 100 // Set the Y position
composeView.setViewTreeLifecycleOwner(LocalLifecycleOwner.current)
composeView.setViewTreeSavedStateRegistryOwner(LocalSavedStateRegistryOwner.current)

windowManager.addView(composeView, params)

}

@composable
fun FloatingButton(onClick: () -> Unit) {
FloatingActionButton(
onClick = onClick,
modifier = androidx.compose.ui.Modifier.padding(16.dp)
) {
Text(text = "Floating Overlay")
}
}

@GC-public-projects
Copy link

@tberghuis

Thanks for this, I used this code to create a floating timer https://github.com/tberghuis/FloatingCountdownTimer

HI ! thanks a lot for your beautiful project that helped me to understand some things. the readme and the documentation are quite short. So as the project has a lot of dependency injections it took me a while to understand the overlay mechanism. Do you have a site or a place you posted a kind of step by step documentation build or something else ?

@tberghuis
Copy link

@GC-public-projects there is no documentation as my implementation is hacked together using this gist and some stackoverflow answers.

I think Google should provide an official sample implementation now that XML views are no longer encouraged.

@farhanlaskani
Copy link

farhanlaskani commented Jul 13, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment