blob: 55c7ac9fb0cc7ce84e3f65c32bb4525002be70ab [file] [log] [blame]
package com.android.systemui.lifecycle
import android.view.View
import android.view.ViewTreeObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
/**
* [LifecycleOwner] for Window-added Views.
*
* These are [View] instances that are added to a `Window` using the `WindowManager` API.
*
* This implementation goes to:
* * The <b>CREATED</b> `Lifecycle.State` when the view gets attached to the window but the window
* is not yet visible
* * The <b>STARTED</b> `Lifecycle.State` when the view is attached to the window and the window is
* visible
* * The <b>RESUMED</b> `Lifecycle.State` when the view is attached to the window and the window is
* visible and the window receives focus
*
* In table format:
* ```
* | ----------------------------------------------------------------------------- |
* | View attached to window | Window visible | Window has focus | Lifecycle state |
* | ----------------------------------------------------------------------------- |
* | not attached | Any | INITIALIZED |
* | ----------------------------------------------------------------------------- |
* | | not visible | Any | CREATED |
* | ----------------------------------------------------- |
* | attached | | not focused | STARTED |
* | | is visible |----------------------------------- |
* | | | has focus | RESUMED |
* | ----------------------------------------------------------------------------- |
* ```
* ### Notes
* * [dispose] must be invoked when the [LifecycleOwner] is done and won't be reused
* * It is always better for [LifecycleOwner] implementations to be more explicit than just
* listening to the state of the `Window`. E.g. if the code that added the `View` to the `Window`
* already has access to the correct state to know when that `View` should become visible and when
* it is ready to receive interaction from the user then it already knows when to move to `STARTED`
* and `RESUMED`, respectively. In that case, it's better to implement your own `LifecycleOwner`
* instead of relying on the `Window` callbacks.
*/
class WindowAddedViewLifecycleOwner
@JvmOverloads
constructor(
private val view: View,
registryFactory: (LifecycleOwner) -> LifecycleRegistry = { LifecycleRegistry(it) },
) : LifecycleOwner {
private val windowAttachListener =
object : ViewTreeObserver.OnWindowAttachListener {
override fun onWindowAttached() {
updateCurrentState()
}
override fun onWindowDetached() {
updateCurrentState()
}
}
private val windowFocusListener =
ViewTreeObserver.OnWindowFocusChangeListener { updateCurrentState() }
private val windowVisibilityListener =
ViewTreeObserver.OnWindowVisibilityChangeListener { updateCurrentState() }
private val registry = registryFactory(this)
init {
setCurrentState(Lifecycle.State.INITIALIZED)
with(view.viewTreeObserver) {
addOnWindowAttachListener(windowAttachListener)
addOnWindowVisibilityChangeListener(windowVisibilityListener)
addOnWindowFocusChangeListener(windowFocusListener)
}
updateCurrentState()
}
override fun getLifecycle(): Lifecycle {
return registry
}
/**
* Disposes of this [LifecycleOwner], performing proper clean-up.
*
* <p>Invoke this when the instance is finished and won't be reused.
*/
fun dispose() {
with(view.viewTreeObserver) {
removeOnWindowAttachListener(windowAttachListener)
removeOnWindowVisibilityChangeListener(windowVisibilityListener)
removeOnWindowFocusChangeListener(windowFocusListener)
}
}
private fun updateCurrentState() {
val state =
when {
!view.isAttachedToWindow -> Lifecycle.State.INITIALIZED
view.windowVisibility != View.VISIBLE -> Lifecycle.State.CREATED
!view.hasWindowFocus() -> Lifecycle.State.STARTED
else -> Lifecycle.State.RESUMED
}
setCurrentState(state)
}
private fun setCurrentState(state: Lifecycle.State) {
if (registry.currentState != state) {
registry.currentState = state
}
}
}