Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 58cd6126 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[multi-shade] Bouncer touch integration.

Connects multi-shade's touch interaction to the bouncer.

On the lock screen such that dragging up when shades are collapsed brings up the bouncer.

https://drive.google.com/file/d/1AiRelfhAg2QFY-EoHvHtG8V6KIzcST_g/view?usp=sharing

Bug: 274159734
Flag: DUAL_SHADE
Test: unit tests expanded for MultiShadeEventInteractor
Test: manually verified that:
1. dragging up when shades are collapsed brings up the bouncer.
2. back gesture hides the bouncer, allowing a drag down to show the dual
   shades again.
3. unlocking the bouncer works as expected.

Change-Id: I81118cb2ccba3bb2887def2568403198e7852c97
parent 1197d0e1
Loading
Loading
Loading
Loading
+95 −27
Original line number Original line Diff line number Diff line
@@ -19,13 +19,20 @@ package com.android.systemui.multishade.domain.interactor
import android.content.Context
import android.content.Context
import android.view.MotionEvent
import android.view.MotionEvent
import android.view.ViewConfiguration
import android.view.ViewConfiguration
import com.android.systemui.classifier.Classifier
import com.android.systemui.dagger.qualifiers.Application
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.multishade.shared.model.ProxiedInputModel
import com.android.systemui.plugins.FalsingManager
import javax.inject.Inject
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.abs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.stateIn


/**
/**
@@ -39,11 +46,23 @@ class MultiShadeMotionEventInteractor
constructor(
constructor(
    @Application private val applicationContext: Context,
    @Application private val applicationContext: Context,
    @Application private val applicationScope: CoroutineScope,
    @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> =
    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,
                scope = applicationScope,
                started = SharingStarted.Eagerly,
                started = SharingStarted.Eagerly,
                initialValue = false,
                initialValue = false,
@@ -65,6 +84,10 @@ constructor(
            return false
            return false
        }
        }


        if (isBouncerShowing.value) {
            return false
        }

        return when (event.actionMasked) {
        return when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
            MotionEvent.ACTION_DOWN -> {
                // Record where the pointer was placed and which pointer it was.
                // Record where the pointer was placed and which pointer it was.
@@ -75,7 +98,7 @@ constructor(
                        currentY = event.y,
                        currentY = event.y,
                        pointerId = event.getPointerId(0),
                        pointerId = event.getPointerId(0),
                        isDraggingHorizontally = false,
                        isDraggingHorizontally = false,
                        isDraggingVertically = false,
                        draggedVertically = Dragged.NONE,
                    )
                    )


                false
                false
@@ -85,16 +108,25 @@ constructor(
                    val pointerIndex = event.findPointerIndex(it.pointerId)
                    val pointerIndex = event.findPointerIndex(it.pointerId)
                    val currentX = event.getX(pointerIndex)
                    val currentX = event.getX(pointerIndex)
                    val currentY = event.getY(pointerIndex)
                    val currentY = event.getY(pointerIndex)
                    if (!it.isDraggingHorizontally && !it.isDraggingVertically) {
                    if (!it.isDraggingHorizontally && it.draggedVertically == Dragged.NONE) {
                        val xDistanceTravelled = abs(currentX - it.initialX)
                        val xDistanceTravelled = currentX - it.initialX
                        val yDistanceTravelled = abs(currentY - it.initialY)
                        val yDistanceTravelled = currentY - it.initialY
                        val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
                        val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
                        interactionState =
                        interactionState =
                            when {
                            when {
                                yDistanceTravelled > touchSlop ->
                                abs(yDistanceTravelled) > touchSlop ->
                                    it.copy(isDraggingVertically = true)
                                    it.copy(
                                xDistanceTravelled > touchSlop ->
                                        draggedVertically =
                                    it.copy(isDraggingHorizontally = true)
                                            if (yDistanceTravelled > 0) {
                                                Dragged.SHADE
                                            } else {
                                                Dragged.BOUNCER
                                            }
                                    )
                                abs(xDistanceTravelled) > touchSlop ->
                                    it.copy(
                                        isDraggingHorizontally = true,
                                    )
                                else -> interactionState
                                else -> interactionState
                            }
                            }
                    }
                    }
@@ -124,7 +156,7 @@ constructor(
        return when (event.actionMasked) {
        return when (event.actionMasked) {
            MotionEvent.ACTION_MOVE -> {
            MotionEvent.ACTION_MOVE -> {
                interactionState?.let {
                interactionState?.let {
                    if (it.isDraggingVertically) {
                    if (it.draggedVertically != Dragged.NONE) {
                        val pointerIndex = event.findPointerIndex(it.pointerId)
                        val pointerIndex = event.findPointerIndex(it.pointerId)
                        val previousY = it.currentY
                        val previousY = it.currentY
                        val currentY = event.getY(pointerIndex)
                        val currentY = event.getY(pointerIndex)
@@ -133,32 +165,48 @@ constructor(
                                currentY = currentY,
                                currentY = currentY,
                            )
                            )


                        when (it.draggedVertically) {
                            Dragged.SHADE -> {
                                val yDragAmountPx = currentY - previousY
                                val yDragAmountPx = currentY - previousY

                                if (yDragAmountPx != 0f) {
                                if (yDragAmountPx != 0f) {
                            interactor.sendProxiedInput(
                                    multiShadeInteractor.sendProxiedInput(
                                        ProxiedInputModel.OnDrag(
                                        ProxiedInputModel.OnDrag(
                                            xFraction = event.x / viewWidthPx,
                                            xFraction = event.x / viewWidthPx,
                                            yDragAmountPx = yDragAmountPx,
                                            yDragAmountPx = yDragAmountPx,
                                        )
                                        )
                                    )
                                    )
                                }
                                }
                                true
                            }
                            }
                            Dragged.BOUNCER -> {
                                bouncerInteractor.show(isScrimmed = true)
                                false
                            }
                            }

                            else -> false
                true
                        }
                    } else {
                        false
                    }
                }
                    ?: false
            }
            }
            MotionEvent.ACTION_UP -> {
            MotionEvent.ACTION_UP -> {
                if (interactionState.isDraggingVertically()) {
                if (interactionState?.draggedVertically == Dragged.SHADE) {
                    // We finished dragging. Record that so the multi-shade framework can issue a
                    // 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.
                    // 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
                interactionState = null
                true
                true
            }
            }
            MotionEvent.ACTION_CANCEL -> {
            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
                    // 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
                    // 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
                    // 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.
                    // 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
                    // Doing that allows the multi-shade framework to know that the gesture ended to
                    // allow new gestures to be accepted.
                    // 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
                interactionState = null
@@ -181,11 +237,23 @@ constructor(
        val initialY: Float,
        val initialY: Float,
        val currentY: Float,
        val currentY: Float,
        val pointerId: Int,
        val pointerId: Int,
        /** Whether the current gesture is dragging horizontally. */
        val isDraggingHorizontally: Boolean,
        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 {
    private fun InteractionState?.isDraggingVertically(): Boolean {
        return this?.isDraggingVertically == true
        return this?.draggedVertically != Dragged.NONE
    }
    }
}
}
+9 −3
Original line number Original line Diff line number Diff line
@@ -233,6 +233,8 @@ import com.android.systemui.util.Utils;
import com.android.systemui.util.time.SystemClock;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.animation.FlingAnimationUtils;


import kotlin.Unit;

import java.io.PrintWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
@@ -243,7 +245,6 @@ import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Provider;


import kotlin.Unit;
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.CoroutineDispatcher;


@CentralSurfacesComponent.CentralSurfacesScope
@CentralSurfacesComponent.CentralSurfacesScope
@@ -387,6 +388,7 @@ public final class NotificationPanelViewController implements Dumpable {
    private KeyguardBottomAreaView mKeyguardBottomArea;
    private KeyguardBottomAreaView mKeyguardBottomArea;
    private boolean mExpanding;
    private boolean mExpanding;
    private boolean mSplitShadeEnabled;
    private boolean mSplitShadeEnabled;
    private final boolean mMultiShadeEnabled;
    /** The bottom padding reserved for elements of the keyguard measuring notifications. */
    /** The bottom padding reserved for elements of the keyguard measuring notifications. */
    private float mKeyguardNotificationBottomPadding;
    private float mKeyguardNotificationBottomPadding;
    /**
    /**
@@ -851,6 +853,7 @@ public final class NotificationPanelViewController implements Dumpable {
        mFeatureFlags = featureFlags;
        mFeatureFlags = featureFlags;
        mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
        mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
        mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
        mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
        mMultiShadeEnabled = mFeatureFlags.isEnabled(Flags.DUAL_SHADE);
        mFalsingCollector = falsingCollector;
        mFalsingCollector = falsingCollector;
        mPowerManager = powerManager;
        mPowerManager = powerManager;
        mWakeUpCoordinator = coordinator;
        mWakeUpCoordinator = coordinator;
@@ -4018,8 +4021,11 @@ public final class NotificationPanelViewController implements Dumpable {
     *   {@link #updateVisibility()}? That would allow us to make this method private.
     *   {@link #updateVisibility()}? That would allow us to make this method private.
     */
     */
    public void updatePanelExpansionAndVisibility() {
    public void updatePanelExpansionAndVisibility() {
        if (!mMultiShadeEnabled) {
            mShadeExpansionStateManager.onPanelExpansionChanged(
            mShadeExpansionStateManager.onPanelExpansionChanged(
                    mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
                    mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
        }

        updateVisibility();
        updateVisibility();
    }
    }


+163 −3
Original line number Original line Diff line number Diff line
@@ -20,7 +20,14 @@ import android.view.MotionEvent
import android.view.ViewConfiguration
import android.view.ViewConfiguration
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.coroutines.collectLastValue
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.remoteproxy.MultiShadeInputProxy
import com.android.systemui.multishade.data.repository.MultiShadeRepository
import com.android.systemui.multishade.data.repository.MultiShadeRepository
import com.android.systemui.multishade.shared.model.ProxiedInputModel
import com.android.systemui.multishade.shared.model.ProxiedInputModel
@@ -36,12 +43,18 @@ import org.junit.Before
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
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)
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@SmallTest
@RunWith(JUnit4::class)
@RunWith(JUnit4::class)
class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
class MultiShadeMotionEventInteractorTest : SysuiTestCase() {


    @Mock private lateinit var bouncerInteractor: PrimaryBouncerInteractor

    private lateinit var underTest: MultiShadeMotionEventInteractor
    private lateinit var underTest: MultiShadeMotionEventInteractor


    private lateinit var testScope: TestScope
    private lateinit var testScope: TestScope
@@ -49,9 +62,13 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
    private lateinit var repository: MultiShadeRepository
    private lateinit var repository: MultiShadeRepository
    private lateinit var interactor: MultiShadeInteractor
    private lateinit var interactor: MultiShadeInteractor
    private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
    private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
    private lateinit var falsingManager: FalsingManagerFake


    @Before
    @Before
    fun setUp() {
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        testScope = TestScope()
        testScope = TestScope()
        motionEvents = mutableSetOf()
        motionEvents = mutableSetOf()


@@ -67,11 +84,19 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
                repository = repository,
                repository = repository,
                inputProxy = inputProxy,
                inputProxy = inputProxy,
            )
            )
        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
        falsingManager = FalsingManagerFake()
        underTest =
        underTest =
            MultiShadeMotionEventInteractor(
            MultiShadeMotionEventInteractor(
                applicationContext = context,
                applicationContext = context,
                applicationScope = testScope.backgroundScope,
                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()
            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
    @Test
    fun tap_doesNotSendProxiedInput() =
    fun tap_doesNotSendProxiedInput() =
        testScope.runTest {
        testScope.runTest {
@@ -233,7 +312,7 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
        }
        }


    @Test
    @Test
    fun dragAboveTouchSlopAndUp() =
    fun dragShadeAboveTouchSlopAndUp() =
        testScope.runTest {
        testScope.runTest {
            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
@@ -277,7 +356,7 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
        }
        }


    @Test
    @Test
    fun dragAboveTouchSlopAndCancel() =
    fun dragShadeAboveTouchSlopAndCancel() =
        testScope.runTest {
        testScope.runTest {
            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
@@ -320,6 +399,87 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
            assertThat(singleShadeProxiedInput).isNull()
            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(
    private fun TestScope.motionEvent(
        action: Int,
        action: Int,
        downTime: Long = currentTime,
        downTime: Long = currentTime,
+10 −2
Original line number Original line Diff line number Diff line
@@ -27,10 +27,12 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.R
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dock.DockManager
import com.android.systemui.dock.DockManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
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.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
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.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations


@OptIn(ExperimentalCoroutinesApi::class)
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@SmallTest
@@ -170,7 +172,13 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
                    MultiShadeMotionEventInteractor(
                    MultiShadeMotionEventInteractor(
                        applicationContext = context,
                        applicationContext = context,
                        applicationScope = testScope.backgroundScope,
                        applicationScope = testScope.backgroundScope,
                        interactor = multiShadeInteractor,
                        multiShadeInteractor = multiShadeInteractor,
                        keyguardTransitionInteractor =
                            KeyguardTransitionInteractor(
                                repository = FakeKeyguardTransitionRepository(),
                            ),
                        bouncerInteractor = com.android.systemui.util.mockito.mock(),
                        falsingManager = FalsingManagerFake(),
                    )
                    )
                },
                },
            )
            )
+9 −1
Original line number Original line Diff line number Diff line
@@ -27,10 +27,12 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.R
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dock.DockManager
import com.android.systemui.dock.DockManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
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.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
@@ -182,7 +184,13 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
                    MultiShadeMotionEventInteractor(
                    MultiShadeMotionEventInteractor(
                        applicationContext = context,
                        applicationContext = context,
                        applicationScope = testScope.backgroundScope,
                        applicationScope = testScope.backgroundScope,
                        interactor = multiShadeInteractor,
                        multiShadeInteractor = multiShadeInteractor,
                        keyguardTransitionInteractor =
                            KeyguardTransitionInteractor(
                                repository = FakeKeyguardTransitionRepository(),
                            ),
                        bouncerInteractor = mock(),
                        falsingManager = FalsingManagerFake(),
                    )
                    )
                },
                },
            )
            )