diff options
8 files changed, 491 insertions, 176 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 3c0ec350c5c5..aabd212c1bd3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -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 + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt index ff7c9015eef4..33b3087678e1 100644 --- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt @@ -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 } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 1cb314a5986e..636058bd9717 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -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(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index 3d13d8092651..276b3e39180b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -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) - - 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)) - - steps.forEach { repository.sendTransitionStep(it) } - - assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5)) - assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8)) - - job1.cancel() - job2.cancel() + fun `transition collectors receives only appropriate events`() = runTest { + val lockscreenToAodSteps by collectValues(underTest.lockscreenToAodTransition) + val aodToLockscreenSteps by collectValues(underTest.aodToLockscreenTransition) + + 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) + runCurrent() } - @Test - fun dozeAmountTransitionTest() = - runTest(UnconfinedTestDispatcher()) { - var dozeAmountSteps = mutableListOf<TransitionStep>() - val job = underTest.dozeAmountTransition.onEach { dozeAmountSteps.add(it) }.launchIn(this) - - 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.forEach { repository.sendTransitionStep(it) } - - 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(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5)) + assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8)) + } @Test - fun keyguardStateTests() = - runTest(UnconfinedTestDispatcher()) { - var finishedSteps = mutableListOf<KeyguardState>() - val job = underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this) - - 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.forEach { repository.sendTransitionStep(it) } - - assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD)) - - job.cancel() + fun dozeAmountTransitionTest() = runTest { + val dozeAmountSteps by collectValues(underTest.dozeAmountTransition) + + 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.forEach { + repository.sendTransitionStep(it) + runCurrent() } - @Test - fun finishedKeyguardTransitionStepTests() = - runTest(UnconfinedTestDispatcher()) { - var finishedSteps = mutableListOf<TransitionStep>() - val job = - underTest.finishedKeyguardTransitionStep.onEach { finishedSteps.add(it) }.launchIn(this) - - 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.forEach { repository.sendTransitionStep(it) } - - assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5])) + 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() + @Test + fun keyguardStateTests() = runTest { + val finishedSteps by collectValues(underTest.finishedKeyguardState) + + 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.forEach { + repository.sendTransitionStep(it) + runCurrent() } + assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD)) + } + @Test - fun startedKeyguardTransitionStepTests() = - runTest(UnconfinedTestDispatcher()) { - var startedSteps = mutableListOf<TransitionStep>() - val job = - underTest.startedKeyguardTransitionStep.onEach { startedSteps.add(it) }.launchIn(this) + fun finishedKeyguardTransitionStepTests() = runTest { + val finishedSteps by collectValues(underTest.finishedKeyguardTransitionStep) + + 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.forEach { + repository.sendTransitionStep(it) + runCurrent() + } - val steps = mutableListOf<TransitionStep>() + assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5])) + } - 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)) + @Test + fun startedKeyguardTransitionStepTests() = runTest { + val startedSteps by collectValues(underTest.startedKeyguardTransitionStep) + + 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.forEach { + repository.sendTransitionStep(it) + runCurrent() + } - steps.forEach { repository.sendTransitionStep(it) } + assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6])) + } - 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() + } - job.cancel() + 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)) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt index f807146cdf12..c6db565d7954 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt @@ -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, ) } @@ -202,6 +227,60 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { } @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 { val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT)) @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 5f34b2f0f87f..006a7c735550 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -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(), ) }, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index b40181e24e6d..1a52067e8306 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -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(), ) }, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt index c2947b42f56d..e1ba074ac860 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt @@ -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 +} + +/** @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 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() } |