Merge "Window-added View LifecycleOwner" into tm-qpr-dev am: 0c97e28823
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19264427 Change-Id: I5b376ae2efe03eac2c706d8e13b38c4648ffcdb0 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
committed by
Automerger Merge Worker
commit
e422c5ab46
@@ -0,0 +1,114 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package com.android.systemui.lifecycle
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleRegistry
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
|
import com.android.systemui.util.mockito.capture
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
class WindowAddedViewLifecycleOwnerTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock lateinit var view: View
|
||||||
|
@Mock lateinit var viewTreeObserver: ViewTreeObserver
|
||||||
|
|
||||||
|
private lateinit var underTest: WindowAddedViewLifecycleOwner
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
|
||||||
|
whenever(view.isAttachedToWindow).thenReturn(false)
|
||||||
|
whenever(view.windowVisibility).thenReturn(View.INVISIBLE)
|
||||||
|
whenever(view.hasWindowFocus()).thenReturn(false)
|
||||||
|
|
||||||
|
underTest = WindowAddedViewLifecycleOwner(view) { LifecycleRegistry.createUnsafe(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `detached - invisible - does not have focus -- INITIALIZED`() {
|
||||||
|
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `detached - invisible - has focus -- INITIALIZED`() {
|
||||||
|
whenever(view.hasWindowFocus()).thenReturn(true)
|
||||||
|
val captor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(captor))
|
||||||
|
captor.value.onWindowFocusChanged(true)
|
||||||
|
|
||||||
|
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `detached - visible - does not have focus -- INITIALIZED`() {
|
||||||
|
whenever(view.windowVisibility).thenReturn(View.VISIBLE)
|
||||||
|
val captor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(captor))
|
||||||
|
captor.value.onWindowVisibilityChanged(View.VISIBLE)
|
||||||
|
|
||||||
|
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `detached - visible - has focus -- INITIALIZED`() {
|
||||||
|
whenever(view.hasWindowFocus()).thenReturn(true)
|
||||||
|
val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
|
||||||
|
focusCaptor.value.onWindowFocusChanged(true)
|
||||||
|
|
||||||
|
whenever(view.windowVisibility).thenReturn(View.VISIBLE)
|
||||||
|
val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
|
||||||
|
visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
|
||||||
|
|
||||||
|
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `attached - invisible - does not have focus -- CREATED`() {
|
||||||
|
whenever(view.isAttachedToWindow).thenReturn(true)
|
||||||
|
val captor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowAttachListener(capture(captor))
|
||||||
|
captor.value.onWindowAttached()
|
||||||
|
|
||||||
|
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `attached - invisible - has focus -- CREATED`() {
|
||||||
|
whenever(view.isAttachedToWindow).thenReturn(true)
|
||||||
|
val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
|
||||||
|
attachCaptor.value.onWindowAttached()
|
||||||
|
|
||||||
|
whenever(view.hasWindowFocus()).thenReturn(true)
|
||||||
|
val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
|
||||||
|
focusCaptor.value.onWindowFocusChanged(true)
|
||||||
|
|
||||||
|
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `attached - visible - does not have focus -- STARTED`() {
|
||||||
|
whenever(view.isAttachedToWindow).thenReturn(true)
|
||||||
|
val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
|
||||||
|
attachCaptor.value.onWindowAttached()
|
||||||
|
|
||||||
|
whenever(view.windowVisibility).thenReturn(View.VISIBLE)
|
||||||
|
val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
|
||||||
|
visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
|
||||||
|
|
||||||
|
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `attached - visible - has focus -- RESUMED`() {
|
||||||
|
whenever(view.isAttachedToWindow).thenReturn(true)
|
||||||
|
val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
|
||||||
|
attachCaptor.value.onWindowAttached()
|
||||||
|
|
||||||
|
whenever(view.hasWindowFocus()).thenReturn(true)
|
||||||
|
val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
|
||||||
|
focusCaptor.value.onWindowFocusChanged(true)
|
||||||
|
|
||||||
|
whenever(view.windowVisibility).thenReturn(View.VISIBLE)
|
||||||
|
val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
|
||||||
|
verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
|
||||||
|
visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
|
||||||
|
|
||||||
|
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dispose() {
|
||||||
|
underTest.dispose()
|
||||||
|
|
||||||
|
verify(viewTreeObserver).removeOnWindowAttachListener(any())
|
||||||
|
verify(viewTreeObserver).removeOnWindowVisibilityChangeListener(any())
|
||||||
|
verify(viewTreeObserver).removeOnWindowFocusChangeListener(any())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user