-
-
Save handstandsam/6ecff2f39da72c0b38c07aa80bbb5a2f to your computer and use it in GitHub Desktop.
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 | |
} | |
} |
Does anyone know how to handle on back button pressed in this service to close the service/remove the overlay when the back button is pressed?
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")
}
}
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 ?
@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.
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") } }
This solution works for me, thanks a lot. Saved me two days.
Reached the same conclusions on some of the comments, specially the updated APIs and the requirement to trigger the START and RESUME lifecycle events so recomposition worked. I found the snippet here, that's why I was unable to find the updates suggested by commenters: https://www.jetpackcompose.app/snippets/OverlayService See my code here: https://github.com/xrubioj/JetpackComposeOverlayTest