Merge changes I81118cb2,Iab6a7568,I9f7ff736 into udc-dev
* changes: [multi-shade] Bouncer touch integration. KeyguardTransitionInteractor#transitionValue. Adds Flow extension for collecting values into a list.
This commit is contained in:
@@ -39,7 +39,7 @@ import kotlinx.coroutines.flow.merge
|
||||
class KeyguardTransitionInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
repository: KeyguardTransitionRepository,
|
||||
private val repository: KeyguardTransitionRepository,
|
||||
) {
|
||||
/** (any)->GONE transition information */
|
||||
val anyStateToGoneTransition: Flow<TransitionStep> =
|
||||
@@ -62,10 +62,6 @@ constructor(
|
||||
/** LOCKSCREEN->AOD transition information. */
|
||||
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
|
||||
|
||||
/** LOCKSCREEN->PRIMARY_BOUNCER transition information. */
|
||||
val mLockscreenToPrimaryBouncerTransition: Flow<TransitionStep> =
|
||||
repository.transition(LOCKSCREEN, PRIMARY_BOUNCER)
|
||||
|
||||
/** LOCKSCREEN->DREAMING transition information. */
|
||||
val lockscreenToDreamingTransition: Flow<TransitionStep> =
|
||||
repository.transition(LOCKSCREEN, DREAMING)
|
||||
@@ -92,19 +88,39 @@ constructor(
|
||||
lockscreenToAodTransition,
|
||||
)
|
||||
|
||||
/* The last [TransitionStep] with a [TransitionState] of STARTED */
|
||||
/** The last [TransitionStep] with a [TransitionState] of STARTED */
|
||||
val startedKeyguardTransitionStep: Flow<TransitionStep> =
|
||||
repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
|
||||
|
||||
/* The last [TransitionStep] with a [TransitionState] of CANCELED */
|
||||
/** The last [TransitionStep] with a [TransitionState] of CANCELED */
|
||||
val canceledKeyguardTransitionStep: Flow<TransitionStep> =
|
||||
repository.transitions.filter { step -> step.transitionState == TransitionState.CANCELED }
|
||||
|
||||
/* The last [TransitionStep] with a [TransitionState] of FINISHED */
|
||||
/** The last [TransitionStep] with a [TransitionState] of FINISHED */
|
||||
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
|
||||
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
|
||||
|
||||
/* The last completed [KeyguardState] transition */
|
||||
/** The last completed [KeyguardState] transition */
|
||||
val finishedKeyguardState: Flow<KeyguardState> =
|
||||
finishedKeyguardTransitionStep.map { step -> step.to }
|
||||
|
||||
/**
|
||||
* The amount of transition into or out of the given [KeyguardState].
|
||||
*
|
||||
* The value will be `0` (or close to `0`, due to float point arithmetic) if not in this step or
|
||||
* `1` when fully in the given state.
|
||||
*/
|
||||
fun transitionValue(
|
||||
state: KeyguardState,
|
||||
): Flow<Float> {
|
||||
return repository.transitions
|
||||
.filter { it.from == state || it.to == state }
|
||||
.map {
|
||||
if (it.from == state) {
|
||||
1 - it.value
|
||||
} else {
|
||||
it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,20 @@ package com.android.systemui.multishade.domain.interactor
|
||||
import android.content.Context
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewConfiguration
|
||||
import com.android.systemui.classifier.Classifier
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState
|
||||
import com.android.systemui.multishade.shared.math.isZero
|
||||
import com.android.systemui.multishade.shared.model.ProxiedInputModel
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.abs
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
/**
|
||||
@@ -39,15 +46,27 @@ class MultiShadeMotionEventInteractor
|
||||
constructor(
|
||||
@Application private val applicationContext: Context,
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
private val interactor: MultiShadeInteractor,
|
||||
private val multiShadeInteractor: MultiShadeInteractor,
|
||||
keyguardTransitionInteractor: KeyguardTransitionInteractor,
|
||||
private val bouncerInteractor: PrimaryBouncerInteractor,
|
||||
private val falsingManager: FalsingManager,
|
||||
) {
|
||||
|
||||
private val isAnyShadeExpanded: StateFlow<Boolean> =
|
||||
interactor.isAnyShadeExpanded.stateIn(
|
||||
multiShadeInteractor.isAnyShadeExpanded.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = false,
|
||||
)
|
||||
private val isBouncerShowing: StateFlow<Boolean> =
|
||||
keyguardTransitionInteractor
|
||||
.transitionValue(state = KeyguardState.PRIMARY_BOUNCER)
|
||||
.map { !it.isZero() }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
private var interactionState: InteractionState? = null
|
||||
|
||||
@@ -65,6 +84,10 @@ constructor(
|
||||
return false
|
||||
}
|
||||
|
||||
if (isBouncerShowing.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
return when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
// Record where the pointer was placed and which pointer it was.
|
||||
@@ -75,7 +98,7 @@ constructor(
|
||||
currentY = event.y,
|
||||
pointerId = event.getPointerId(0),
|
||||
isDraggingHorizontally = false,
|
||||
isDraggingVertically = false,
|
||||
draggedVertically = Dragged.NONE,
|
||||
)
|
||||
|
||||
false
|
||||
@@ -85,16 +108,25 @@ constructor(
|
||||
val pointerIndex = event.findPointerIndex(it.pointerId)
|
||||
val currentX = event.getX(pointerIndex)
|
||||
val currentY = event.getY(pointerIndex)
|
||||
if (!it.isDraggingHorizontally && !it.isDraggingVertically) {
|
||||
val xDistanceTravelled = abs(currentX - it.initialX)
|
||||
val yDistanceTravelled = abs(currentY - it.initialY)
|
||||
if (!it.isDraggingHorizontally && it.draggedVertically == Dragged.NONE) {
|
||||
val xDistanceTravelled = currentX - it.initialX
|
||||
val yDistanceTravelled = currentY - it.initialY
|
||||
val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
|
||||
interactionState =
|
||||
when {
|
||||
yDistanceTravelled > touchSlop ->
|
||||
it.copy(isDraggingVertically = true)
|
||||
xDistanceTravelled > touchSlop ->
|
||||
it.copy(isDraggingHorizontally = true)
|
||||
abs(yDistanceTravelled) > touchSlop ->
|
||||
it.copy(
|
||||
draggedVertically =
|
||||
if (yDistanceTravelled > 0) {
|
||||
Dragged.SHADE
|
||||
} else {
|
||||
Dragged.BOUNCER
|
||||
}
|
||||
)
|
||||
abs(xDistanceTravelled) > touchSlop ->
|
||||
it.copy(
|
||||
isDraggingHorizontally = true,
|
||||
)
|
||||
else -> interactionState
|
||||
}
|
||||
}
|
||||
@@ -124,7 +156,7 @@ constructor(
|
||||
return when (event.actionMasked) {
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
interactionState?.let {
|
||||
if (it.isDraggingVertically) {
|
||||
if (it.draggedVertically != Dragged.NONE) {
|
||||
val pointerIndex = event.findPointerIndex(it.pointerId)
|
||||
val previousY = it.currentY
|
||||
val currentY = event.getY(pointerIndex)
|
||||
@@ -133,32 +165,48 @@ constructor(
|
||||
currentY = currentY,
|
||||
)
|
||||
|
||||
val yDragAmountPx = currentY - previousY
|
||||
if (yDragAmountPx != 0f) {
|
||||
interactor.sendProxiedInput(
|
||||
ProxiedInputModel.OnDrag(
|
||||
xFraction = event.x / viewWidthPx,
|
||||
yDragAmountPx = yDragAmountPx,
|
||||
)
|
||||
)
|
||||
when (it.draggedVertically) {
|
||||
Dragged.SHADE -> {
|
||||
val yDragAmountPx = currentY - previousY
|
||||
|
||||
if (yDragAmountPx != 0f) {
|
||||
multiShadeInteractor.sendProxiedInput(
|
||||
ProxiedInputModel.OnDrag(
|
||||
xFraction = event.x / viewWidthPx,
|
||||
yDragAmountPx = yDragAmountPx,
|
||||
)
|
||||
)
|
||||
}
|
||||
true
|
||||
}
|
||||
Dragged.BOUNCER -> {
|
||||
bouncerInteractor.show(isScrimmed = true)
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
?: false
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (interactionState.isDraggingVertically()) {
|
||||
if (interactionState?.draggedVertically == Dragged.SHADE) {
|
||||
// We finished dragging. Record that so the multi-shade framework can issue a
|
||||
// fling, if the velocity reached in the drag was high enough, for example.
|
||||
interactor.sendProxiedInput(ProxiedInputModel.OnDragEnd)
|
||||
multiShadeInteractor.sendProxiedInput(ProxiedInputModel.OnDragEnd)
|
||||
|
||||
if (falsingManager.isFalseTouch(Classifier.SHADE_DRAG)) {
|
||||
multiShadeInteractor.collapseAll()
|
||||
}
|
||||
}
|
||||
|
||||
interactionState = null
|
||||
true
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
if (interactionState.isDraggingVertically()) {
|
||||
if (interactionState?.draggedVertically == Dragged.SHADE) {
|
||||
// Our drag gesture was canceled by the system. This happens primarily in one of
|
||||
// two occasions: (a) the parent view has decided to intercept the gesture
|
||||
// itself and/or route it to a different child view or (b) the pointer has
|
||||
@@ -166,7 +214,15 @@ constructor(
|
||||
// we pass the cancellation event to the multi-shade framework to record it.
|
||||
// Doing that allows the multi-shade framework to know that the gesture ended to
|
||||
// allow new gestures to be accepted.
|
||||
interactor.sendProxiedInput(ProxiedInputModel.OnDragCancel)
|
||||
multiShadeInteractor.sendProxiedInput(ProxiedInputModel.OnDragCancel)
|
||||
|
||||
if (falsingManager.isFalseTouch(Classifier.SHADE_DRAG)) {
|
||||
multiShadeInteractor.collapseAll()
|
||||
}
|
||||
} else if (interactionState?.draggedVertically == Dragged.BOUNCER) {
|
||||
if (falsingManager.isFalseTouch(Classifier.BOUNCER_UNLOCK)) {
|
||||
bouncerInteractor.hide()
|
||||
}
|
||||
}
|
||||
|
||||
interactionState = null
|
||||
@@ -181,11 +237,23 @@ constructor(
|
||||
val initialY: Float,
|
||||
val currentY: Float,
|
||||
val pointerId: Int,
|
||||
/** Whether the current gesture is dragging horizontally. */
|
||||
val isDraggingHorizontally: Boolean,
|
||||
val isDraggingVertically: Boolean,
|
||||
/** The UI component that is being dragged vertically, if any. */
|
||||
val draggedVertically: Dragged,
|
||||
)
|
||||
|
||||
/** Enumerates the UI components that can be dragged by the user. */
|
||||
private enum class Dragged {
|
||||
/** The bouncer is being dragged by the user. */
|
||||
BOUNCER,
|
||||
/** A shade is being dragged by the user. */
|
||||
SHADE,
|
||||
/** No UI component is being dragged by the user. */
|
||||
NONE,
|
||||
}
|
||||
|
||||
private fun InteractionState?.isDraggingVertically(): Boolean {
|
||||
return this?.isDraggingVertically == true
|
||||
return this?.draggedVertically != Dragged.NONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,8 @@ import com.android.systemui.util.Utils;
|
||||
import com.android.systemui.util.time.SystemClock;
|
||||
import com.android.wm.shell.animation.FlingAnimationUtils;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -244,7 +246,6 @@ import java.util.function.Consumer;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import kotlin.Unit;
|
||||
import kotlinx.coroutines.CoroutineDispatcher;
|
||||
|
||||
@CentralSurfacesComponent.CentralSurfacesScope
|
||||
@@ -388,6 +389,7 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
private KeyguardBottomAreaView mKeyguardBottomArea;
|
||||
private boolean mExpanding;
|
||||
private boolean mSplitShadeEnabled;
|
||||
private final boolean mMultiShadeEnabled;
|
||||
/** The bottom padding reserved for elements of the keyguard measuring notifications. */
|
||||
private float mKeyguardNotificationBottomPadding;
|
||||
/**
|
||||
@@ -852,6 +854,7 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
mFeatureFlags = featureFlags;
|
||||
mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
|
||||
mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
|
||||
mMultiShadeEnabled = mFeatureFlags.isEnabled(Flags.DUAL_SHADE);
|
||||
mFalsingCollector = falsingCollector;
|
||||
mPowerManager = powerManager;
|
||||
mWakeUpCoordinator = coordinator;
|
||||
@@ -4021,8 +4024,11 @@ public final class NotificationPanelViewController implements Dumpable {
|
||||
* {@link #updateVisibility()}? That would allow us to make this method private.
|
||||
*/
|
||||
public void updatePanelExpansionAndVisibility() {
|
||||
mShadeExpansionStateManager.onPanelExpansionChanged(
|
||||
mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
|
||||
if (!mMultiShadeEnabled) {
|
||||
mShadeExpansionStateManager.onPanelExpansionChanged(
|
||||
mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
|
||||
}
|
||||
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,10 @@ package com.android.systemui.keyguard.domain.interactor
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.coroutines.collectValues
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
|
||||
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
|
||||
@@ -30,9 +31,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
|
||||
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
|
||||
import com.android.systemui.keyguard.shared.model.TransitionStep
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -53,138 +52,151 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `transition collectors receives only appropriate events`() =
|
||||
runTest(UnconfinedTestDispatcher()) {
|
||||
var lockscreenToAodSteps = mutableListOf<TransitionStep>()
|
||||
val job1 =
|
||||
underTest.lockscreenToAodTransition
|
||||
.onEach { lockscreenToAodSteps.add(it) }
|
||||
.launchIn(this)
|
||||
fun `transition collectors receives only appropriate events`() = runTest {
|
||||
val lockscreenToAodSteps by collectValues(underTest.lockscreenToAodTransition)
|
||||
val aodToLockscreenSteps by collectValues(underTest.aodToLockscreenTransition)
|
||||
|
||||
var aodToLockscreenSteps = mutableListOf<TransitionStep>()
|
||||
val job2 =
|
||||
underTest.aodToLockscreenTransition
|
||||
.onEach { aodToLockscreenSteps.add(it) }
|
||||
.launchIn(this)
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
steps.add(TransitionStep(AOD, GONE, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, GONE, 1f, FINISHED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
|
||||
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
steps.add(TransitionStep(AOD, GONE, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, GONE, 1f, FINISHED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
|
||||
|
||||
steps.forEach { repository.sendTransitionStep(it) }
|
||||
|
||||
assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
|
||||
assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
|
||||
|
||||
job1.cancel()
|
||||
job2.cancel()
|
||||
steps.forEach {
|
||||
repository.sendTransitionStep(it)
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
|
||||
assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dozeAmountTransitionTest() =
|
||||
runTest(UnconfinedTestDispatcher()) {
|
||||
var dozeAmountSteps = mutableListOf<TransitionStep>()
|
||||
val job = underTest.dozeAmountTransition.onEach { dozeAmountSteps.add(it) }.launchIn(this)
|
||||
fun dozeAmountTransitionTest() = runTest {
|
||||
val dozeAmountSteps by collectValues(underTest.dozeAmountTransition)
|
||||
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
|
||||
|
||||
steps.forEach { repository.sendTransitionStep(it) }
|
||||
steps.forEach {
|
||||
repository.sendTransitionStep(it)
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
assertThat(dozeAmountSteps.subList(0, 3))
|
||||
.isEqualTo(
|
||||
listOf(
|
||||
steps[0].copy(value = 1f - steps[0].value),
|
||||
steps[1].copy(value = 1f - steps[1].value),
|
||||
steps[2].copy(value = 1f - steps[2].value),
|
||||
)
|
||||
assertThat(dozeAmountSteps.subList(0, 3))
|
||||
.isEqualTo(
|
||||
listOf(
|
||||
steps[0].copy(value = 1f - steps[0].value),
|
||||
steps[1].copy(value = 1f - steps[1].value),
|
||||
steps[2].copy(value = 1f - steps[2].value),
|
||||
)
|
||||
assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7))
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
)
|
||||
assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keyguardStateTests() =
|
||||
runTest(UnconfinedTestDispatcher()) {
|
||||
var finishedSteps = mutableListOf<KeyguardState>()
|
||||
val job = underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
|
||||
fun keyguardStateTests() = runTest {
|
||||
val finishedSteps by collectValues(underTest.finishedKeyguardState)
|
||||
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
|
||||
steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
|
||||
steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
|
||||
|
||||
steps.forEach { repository.sendTransitionStep(it) }
|
||||
|
||||
assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD))
|
||||
|
||||
job.cancel()
|
||||
steps.forEach {
|
||||
repository.sendTransitionStep(it)
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun finishedKeyguardTransitionStepTests() =
|
||||
runTest(UnconfinedTestDispatcher()) {
|
||||
var finishedSteps = mutableListOf<TransitionStep>()
|
||||
val job =
|
||||
underTest.finishedKeyguardTransitionStep.onEach { finishedSteps.add(it) }.launchIn(this)
|
||||
fun finishedKeyguardTransitionStepTests() = runTest {
|
||||
val finishedSteps by collectValues(underTest.finishedKeyguardTransitionStep)
|
||||
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
|
||||
steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
|
||||
steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
|
||||
|
||||
steps.forEach { repository.sendTransitionStep(it) }
|
||||
|
||||
assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5]))
|
||||
|
||||
job.cancel()
|
||||
steps.forEach {
|
||||
repository.sendTransitionStep(it)
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startedKeyguardTransitionStepTests() =
|
||||
runTest(UnconfinedTestDispatcher()) {
|
||||
var startedSteps = mutableListOf<TransitionStep>()
|
||||
val job =
|
||||
underTest.startedKeyguardTransitionStep.onEach { startedSteps.add(it) }.launchIn(this)
|
||||
fun startedKeyguardTransitionStepTests() = runTest {
|
||||
val startedSteps by collectValues(underTest.startedKeyguardTransitionStep)
|
||||
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
|
||||
steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
|
||||
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
|
||||
steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
|
||||
steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
|
||||
|
||||
steps.forEach { repository.sendTransitionStep(it) }
|
||||
|
||||
assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
|
||||
|
||||
job.cancel()
|
||||
steps.forEach {
|
||||
repository.sendTransitionStep(it)
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun transitionValue() = runTest {
|
||||
val startedSteps by collectValues(underTest.transitionValue(state = DOZING))
|
||||
|
||||
val toSteps =
|
||||
listOf(
|
||||
TransitionStep(AOD, DOZING, 0f, STARTED),
|
||||
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
|
||||
TransitionStep(AOD, DOZING, 1f, FINISHED),
|
||||
)
|
||||
toSteps.forEach {
|
||||
repository.sendTransitionStep(it)
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
val fromSteps =
|
||||
listOf(
|
||||
TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
|
||||
TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
|
||||
TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED),
|
||||
)
|
||||
fromSteps.forEach {
|
||||
repository.sendTransitionStep(it)
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,14 @@ import android.view.MotionEvent
|
||||
import android.view.ViewConfiguration
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.classifier.FalsingManagerFake
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState
|
||||
import com.android.systemui.keyguard.shared.model.TransitionState
|
||||
import com.android.systemui.keyguard.shared.model.TransitionStep
|
||||
import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
|
||||
import com.android.systemui.multishade.data.repository.MultiShadeRepository
|
||||
import com.android.systemui.multishade.shared.model.ProxiedInputModel
|
||||
@@ -36,12 +43,18 @@ 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.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var bouncerInteractor: PrimaryBouncerInteractor
|
||||
|
||||
private lateinit var underTest: MultiShadeMotionEventInteractor
|
||||
|
||||
private lateinit var testScope: TestScope
|
||||
@@ -49,9 +62,13 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
|
||||
private lateinit var repository: MultiShadeRepository
|
||||
private lateinit var interactor: MultiShadeInteractor
|
||||
private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
|
||||
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
|
||||
private lateinit var falsingManager: FalsingManagerFake
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
testScope = TestScope()
|
||||
motionEvents = mutableSetOf()
|
||||
|
||||
@@ -67,11 +84,19 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
|
||||
repository = repository,
|
||||
inputProxy = inputProxy,
|
||||
)
|
||||
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
|
||||
falsingManager = FalsingManagerFake()
|
||||
underTest =
|
||||
MultiShadeMotionEventInteractor(
|
||||
applicationContext = context,
|
||||
applicationScope = testScope.backgroundScope,
|
||||
interactor = interactor,
|
||||
multiShadeInteractor = interactor,
|
||||
keyguardTransitionInteractor =
|
||||
KeyguardTransitionInteractor(
|
||||
repository = keyguardTransitionRepository,
|
||||
),
|
||||
bouncerInteractor = bouncerInteractor,
|
||||
falsingManager = falsingManager,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -201,6 +226,60 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
|
||||
assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldIntercept_moveAboveTouchSlopAndUp_butBouncerShowing_returnsFalse() =
|
||||
testScope.runTest {
|
||||
keyguardTransitionRepository.sendTransitionStep(
|
||||
TransitionStep(
|
||||
from = KeyguardState.LOCKSCREEN,
|
||||
to = KeyguardState.PRIMARY_BOUNCER,
|
||||
value = 0.1f,
|
||||
transitionState = TransitionState.STARTED,
|
||||
)
|
||||
)
|
||||
runCurrent()
|
||||
|
||||
underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
|
||||
|
||||
assertThat(
|
||||
underTest.shouldIntercept(
|
||||
motionEvent(
|
||||
MotionEvent.ACTION_MOVE,
|
||||
y = touchSlop + 1f,
|
||||
)
|
||||
)
|
||||
)
|
||||
.isFalse()
|
||||
assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldIntercept_moveAboveTouchSlopAndCancel_butBouncerShowing_returnsFalse() =
|
||||
testScope.runTest {
|
||||
keyguardTransitionRepository.sendTransitionStep(
|
||||
TransitionStep(
|
||||
from = KeyguardState.LOCKSCREEN,
|
||||
to = KeyguardState.PRIMARY_BOUNCER,
|
||||
value = 0.1f,
|
||||
transitionState = TransitionState.STARTED,
|
||||
)
|
||||
)
|
||||
runCurrent()
|
||||
|
||||
underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
|
||||
|
||||
assertThat(
|
||||
underTest.shouldIntercept(
|
||||
motionEvent(
|
||||
MotionEvent.ACTION_MOVE,
|
||||
y = touchSlop + 1f,
|
||||
)
|
||||
)
|
||||
)
|
||||
.isFalse()
|
||||
assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tap_doesNotSendProxiedInput() =
|
||||
testScope.runTest {
|
||||
@@ -233,7 +312,7 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dragAboveTouchSlopAndUp() =
|
||||
fun dragShadeAboveTouchSlopAndUp() =
|
||||
testScope.runTest {
|
||||
val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
|
||||
val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
|
||||
@@ -277,7 +356,7 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dragAboveTouchSlopAndCancel() =
|
||||
fun dragShadeAboveTouchSlopAndCancel() =
|
||||
testScope.runTest {
|
||||
val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
|
||||
val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
|
||||
@@ -320,6 +399,87 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
|
||||
assertThat(singleShadeProxiedInput).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dragBouncerAboveTouchSlopAndUp_showsBouncer() =
|
||||
testScope.runTest {
|
||||
val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
|
||||
val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
|
||||
val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
|
||||
|
||||
underTest.shouldIntercept(
|
||||
motionEvent(
|
||||
MotionEvent.ACTION_DOWN,
|
||||
x = 100f, // left shade
|
||||
)
|
||||
)
|
||||
assertThat(leftShadeProxiedInput).isNull()
|
||||
assertThat(rightShadeProxiedInput).isNull()
|
||||
assertThat(singleShadeProxiedInput).isNull()
|
||||
|
||||
val yDragAmountPx = -(touchSlop + 1f) // dragging up
|
||||
val moveEvent =
|
||||
motionEvent(
|
||||
MotionEvent.ACTION_MOVE,
|
||||
x = 100f, // left shade
|
||||
y = yDragAmountPx,
|
||||
)
|
||||
assertThat(underTest.shouldIntercept(moveEvent)).isTrue()
|
||||
underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
|
||||
verify(bouncerInteractor).show(isScrimmed = true)
|
||||
assertThat(leftShadeProxiedInput).isNull()
|
||||
assertThat(rightShadeProxiedInput).isNull()
|
||||
assertThat(singleShadeProxiedInput).isNull()
|
||||
|
||||
val upEvent = motionEvent(MotionEvent.ACTION_UP)
|
||||
assertThat(underTest.shouldIntercept(upEvent)).isTrue()
|
||||
underTest.onTouchEvent(upEvent, viewWidthPx = 1000)
|
||||
verify(bouncerInteractor, never()).hide()
|
||||
assertThat(leftShadeProxiedInput).isNull()
|
||||
assertThat(rightShadeProxiedInput).isNull()
|
||||
assertThat(singleShadeProxiedInput).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dragBouncerAboveTouchSlopAndCancel_falseTouch_showsThenHidesBouncer() =
|
||||
testScope.runTest {
|
||||
val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
|
||||
val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
|
||||
val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
|
||||
|
||||
underTest.shouldIntercept(
|
||||
motionEvent(
|
||||
MotionEvent.ACTION_DOWN,
|
||||
x = 900f, // right shade
|
||||
)
|
||||
)
|
||||
assertThat(leftShadeProxiedInput).isNull()
|
||||
assertThat(rightShadeProxiedInput).isNull()
|
||||
assertThat(singleShadeProxiedInput).isNull()
|
||||
|
||||
val yDragAmountPx = -(touchSlop + 1f) // drag up
|
||||
val moveEvent =
|
||||
motionEvent(
|
||||
MotionEvent.ACTION_MOVE,
|
||||
x = 900f, // right shade
|
||||
y = yDragAmountPx,
|
||||
)
|
||||
assertThat(underTest.shouldIntercept(moveEvent)).isTrue()
|
||||
underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
|
||||
verify(bouncerInteractor).show(isScrimmed = true)
|
||||
assertThat(leftShadeProxiedInput).isNull()
|
||||
assertThat(rightShadeProxiedInput).isNull()
|
||||
assertThat(singleShadeProxiedInput).isNull()
|
||||
|
||||
falsingManager.setIsFalseTouch(true)
|
||||
val cancelEvent = motionEvent(MotionEvent.ACTION_CANCEL)
|
||||
assertThat(underTest.shouldIntercept(cancelEvent)).isTrue()
|
||||
underTest.onTouchEvent(cancelEvent, viewWidthPx = 1000)
|
||||
verify(bouncerInteractor).hide()
|
||||
assertThat(leftShadeProxiedInput).isNull()
|
||||
assertThat(rightShadeProxiedInput).isNull()
|
||||
assertThat(singleShadeProxiedInput).isNull()
|
||||
}
|
||||
|
||||
private fun TestScope.motionEvent(
|
||||
action: Int,
|
||||
downTime: Long = currentTime,
|
||||
|
||||
@@ -27,10 +27,12 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.classifier.FalsingCollectorFake
|
||||
import com.android.systemui.classifier.FalsingManagerFake
|
||||
import com.android.systemui.dock.DockManager
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
|
||||
import com.android.systemui.keyguard.shared.model.TransitionStep
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
|
||||
@@ -67,8 +69,8 @@ import org.mockito.Mockito.anyFloat
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@@ -170,7 +172,13 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
|
||||
MultiShadeMotionEventInteractor(
|
||||
applicationContext = context,
|
||||
applicationScope = testScope.backgroundScope,
|
||||
interactor = multiShadeInteractor,
|
||||
multiShadeInteractor = multiShadeInteractor,
|
||||
keyguardTransitionInteractor =
|
||||
KeyguardTransitionInteractor(
|
||||
repository = FakeKeyguardTransitionRepository(),
|
||||
),
|
||||
bouncerInteractor = com.android.systemui.util.mockito.mock(),
|
||||
falsingManager = FalsingManagerFake(),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -27,10 +27,12 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.classifier.FalsingCollectorFake
|
||||
import com.android.systemui.classifier.FalsingManagerFake
|
||||
import com.android.systemui.dock.DockManager
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
|
||||
@@ -182,7 +184,13 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
|
||||
MultiShadeMotionEventInteractor(
|
||||
applicationContext = context,
|
||||
applicationScope = testScope.backgroundScope,
|
||||
interactor = multiShadeInteractor,
|
||||
multiShadeInteractor = multiShadeInteractor,
|
||||
keyguardTransitionInteractor =
|
||||
KeyguardTransitionInteractor(
|
||||
repository = FakeKeyguardTransitionRepository(),
|
||||
),
|
||||
bouncerInteractor = mock(),
|
||||
falsingManager = FalsingManagerFake(),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:Suppress("OPT_IN_USAGE")
|
||||
|
||||
package com.android.systemui.coroutines
|
||||
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@@ -43,20 +45,55 @@ fun <T> TestScope.collectLastValue(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
): FlowValue<T?> {
|
||||
var lastValue: T? = null
|
||||
backgroundScope.launch(context, start) { flow.collect { lastValue = it } }
|
||||
return FlowValueImpl {
|
||||
val values by
|
||||
collectValues(
|
||||
flow = flow,
|
||||
context = context,
|
||||
start = start,
|
||||
)
|
||||
return FlowValueImpl { values.lastOrNull() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect [flow] in a new [Job] and return a getter for the collection of values collected.
|
||||
*
|
||||
* ```
|
||||
* fun myTest() = runTest {
|
||||
* // ...
|
||||
* val values by collectValues(underTest.flow)
|
||||
* assertThat(values).isEqualTo(listOf(expected1, expected2, ...))
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
fun <T> TestScope.collectValues(
|
||||
flow: Flow<T>,
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
): FlowValues<T> {
|
||||
val values = mutableListOf<T>()
|
||||
backgroundScope.launch(context, start) { flow.collect(values::add) }
|
||||
return FlowValuesImpl {
|
||||
runCurrent()
|
||||
lastValue
|
||||
values.toList()
|
||||
}
|
||||
}
|
||||
|
||||
/** @see collectLastValue */
|
||||
interface FlowValue<T> : ReadOnlyProperty<Any?, T?> {
|
||||
operator fun invoke(): T?
|
||||
interface FlowValue<T> : ReadOnlyProperty<Any?, T> {
|
||||
operator fun invoke(): T
|
||||
}
|
||||
|
||||
private class FlowValueImpl<T>(private val block: () -> T?) : FlowValue<T> {
|
||||
override operator fun invoke(): T? = block()
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T? = invoke()
|
||||
/** @see collectValues */
|
||||
interface FlowValues<T> : ReadOnlyProperty<Any?, List<T>> {
|
||||
operator fun invoke(): List<T>
|
||||
}
|
||||
|
||||
private class FlowValueImpl<T>(private val block: () -> T) : FlowValue<T> {
|
||||
override operator fun invoke(): T = block()
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T = invoke()
|
||||
}
|
||||
|
||||
private class FlowValuesImpl<T>(private val block: () -> List<T>) : FlowValues<T> {
|
||||
override operator fun invoke(): List<T> = block()
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> = invoke()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user