[go: nahoru, domu]

blob: d62da1bdb409d1c7288dc19f7f2df2fb1f213a4a [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.ui.test.android
import android.view.View
import android.view.ViewTreeObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.ui.core.AndroidOwner
import java.util.Collections
import java.util.WeakHashMap
/**
* Registry where all [AndroidOwner]s should be registered while they are attached to the window.
* This registry is used by the testing library to query the owners's state.
*/
internal object AndroidOwnerRegistry {
private val owners = Collections.newSetFromMap(WeakHashMap<AndroidOwner, Boolean>())
private val notYetDrawn = Collections.newSetFromMap(WeakHashMap<AndroidOwner, Boolean>())
private var onDrawnCallback: (() -> Unit)? = null
private val registryListeners = mutableSetOf<OnRegistrationChangedListener>()
/**
* Returns if the registry is setup to receive registrations from [AndroidOwner]s
*/
val isSetup: Boolean
get() = AndroidOwner.onAndroidOwnerCreatedCallback == ::onAndroidOwnerCreated
/**
* Sets up this registry to be notified of any [AndroidOwner] created
*/
internal fun setupRegistry() {
AndroidOwner.onAndroidOwnerCreatedCallback = ::onAndroidOwnerCreated
}
/**
* Cleans up the changes made by [setupRegistry]. Call this after your test has run.
*/
internal fun tearDownRegistry() {
AndroidOwner.onAndroidOwnerCreatedCallback = null
}
private fun onAndroidOwnerCreated(owner: AndroidOwner) {
owner.view.addOnAttachStateChangeListener(OwnerAttachedListener(owner))
}
/**
* Returns a copy of the set of all registered [AndroidOwner]s, including ones that are
* normally not relevant (like those whose lifecycle state is not RESUMED).
*/
fun getUnfilteredOwners(): Set<AndroidOwner> {
return owners.toSet()
}
/**
* Returns a copy of the set of all registered [AndroidOwner]s that can be interacted with.
* This method is almost always preferred over [getUnfilteredOwners].
*/
fun getOwners(): Set<AndroidOwner> {
return owners.filterTo(mutableSetOf()) {
// lifecycleOwner can only be null if it.view is not yet attached, and since owners
// are only in the registry when they're attached we don't care about the
// lifecycleOwner being null.
val lifecycleOwner = it.lifecycleOwner ?: return@filterTo false
lifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED
}
}
/**
* Adds the given [listener], to be notified when an [AndroidOwner] registers or unregisters.
*/
fun addOnRegistrationChangedListener(listener: OnRegistrationChangedListener) {
registryListeners.add(listener)
}
/**
* Removes the given [listener].
*/
fun removeOnRegistrationChangedListener(listener: OnRegistrationChangedListener) {
registryListeners.remove(listener)
}
private fun dispatchOnRegistrationChanged(owner: AndroidOwner, isRegistered: Boolean) {
registryListeners.toList().forEach {
it.onRegistrationChanged(owner, isRegistered)
}
}
/**
* Returns if all registered owners have finished at least one draw call.
*/
fun haveAllDrawn(): Boolean {
return notYetDrawn.all {
val lifecycleOwner = ViewTreeLifecycleOwner.get(it.view) ?: return false
lifecycleOwner.lifecycle.currentState != Lifecycle.State.RESUMED
}
}
/**
* Adds a [callback] to be called when all registered [AndroidOwner]s have drawn at least
* once. The callback will be removed after it is called.
*/
fun setOnDrawnCallback(callback: (() -> Unit)?) {
onDrawnCallback = callback
}
/**
* Registers the [owner] in this registry. Must be called from [View.onAttachedToWindow].
*/
internal fun registerOwner(owner: AndroidOwner) {
owners.add(owner)
notYetDrawn.add(owner)
owner.view.viewTreeObserver.addOnDrawListener(FirstDrawListener(owner))
dispatchOnRegistrationChanged(owner, true)
}
/**
* Unregisters the [owner] from this registry. Must be called from [View.onDetachedFromWindow].
*/
internal fun unregisterOwner(owner: AndroidOwner) {
owners.remove(owner)
notYetDrawn.remove(owner)
dispatchOnDrawn()
dispatchOnRegistrationChanged(owner, false)
}
/**
* Should be called when a registered owner has drawn for the first time. Can be called after
* subsequent draws as well, but that is not required.
*/
// TODO(b/155742511): Move all onDrawn functionality to another class, so the registry
// remains purely about tracking AndroidOwners.
private fun notifyOwnerDrawn(owner: AndroidOwner) {
notYetDrawn.remove(owner)
dispatchOnDrawn()
}
private fun dispatchOnDrawn() {
if (haveAllDrawn()) {
onDrawnCallback?.invoke()
onDrawnCallback = null
}
}
/**
* Interface to be implemented by components that want to be notified when an [AndroidOwner]
* registers or unregisters at this registry.
*/
interface OnRegistrationChangedListener {
fun onRegistrationChanged(owner: AndroidOwner, registered: Boolean)
}
private class FirstDrawListener(private val owner: AndroidOwner) :
ViewTreeObserver.OnDrawListener {
private var invoked = false
override fun onDraw() {
if (!invoked) {
invoked = true
owner.view.post {
// the view was drawn
notifyOwnerDrawn(owner)
val viewTreeObserver = owner.view.viewTreeObserver
if (viewTreeObserver.isAlive) {
viewTreeObserver.removeOnDrawListener(this)
}
}
}
}
}
private class OwnerAttachedListener(
private val owner: AndroidOwner
) : View.OnAttachStateChangeListener {
// Note: owner.view === view, because the owner _is_ the view,
// and this listener is only referenced from within the view.
override fun onViewAttachedToWindow(view: View) {
registerOwner(owner)
}
override fun onViewDetachedFromWindow(view: View) {
unregisterOwner(owner)
}
}
}