Merge "Window-added View LifecycleOwner" into tm-qpr-dev am: 0c97e28823 am: e422c5ab46
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19264427 Change-Id: Ie34d5abc4c27670def2ed476eb5bb3c8bddbb89f 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
f19c3f68ac
@@ -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