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

Commit dfde304a authored by William Xiao's avatar William Xiao
Browse files

Fix hub touch interactions with bouncer and shade

The hub's gesture exclusion area was applying even when the bouncer or
shade were open, which prevented back gestures from working on those
surfaces.

The hub's definition of when the bouncer is open also updated only at
the very end of the bouncer open animation, which takes a bit of time
even on a fling. This meant that if you opened the bouncer then quickly
tapped or swiped, you could trigger a widget or even close the hub
underneath the bouncer.

Bug: 333734282
Test: atest CommunalViewModelTest
Flag: ACONFIG com.android.systemui.communal_hub TEAMFOOD
Change-Id: I370fdb947dcd97058a09abdaa3ab0cbe012ee18b
parent 8ddf1c51
Loading
Loading
Loading
Loading
+24 −6
Original line number Diff line number Diff line
@@ -229,7 +229,9 @@ constructor(
        // the gesture area doesn't overlap with widgets.
        // TODO(b/323035776): adjust gesture areaa for portrait mode
        containerView.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
            // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
            // occluded.
            lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) {
                val exclusionRect =
                    Rect(
                        0,
@@ -242,12 +244,19 @@ constructor(
            }
        }

        // Listen to bouncer visibility directly as these flows become true as soon as any portion
        // of the bouncers are visible when the transition starts. The keyguard transition state
        // only changes once transitions are fully finished, which would mean touches during a
        // transition to the bouncer would be incorrectly intercepted by the hub.
        collectFlow(
            containerView,
            keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
            or(
                keyguardInteractor.primaryBouncerShowing,
                keyguardInteractor.alternateBouncerShowing
            ),
            {
                anyBouncerShowing = it
                updateLifecycleState()
                updateTouchHandlingState()
            }
        )
        collectFlow(
@@ -255,7 +264,7 @@ constructor(
            communalInteractor.isCommunalShowing,
            {
                hubShowing = it
                updateLifecycleState()
                updateTouchHandlingState()
            }
        )
        collectFlow(
@@ -263,7 +272,7 @@ constructor(
            and(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)),
            {
                shadeShowing = it
                updateLifecycleState()
                updateTouchHandlingState()
            }
        )
        collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it })
@@ -276,13 +285,22 @@ constructor(
    /**
     * Updates the lifecycle stored by the [lifecycleRegistry] to control when the [touchMonitor]
     * should listen for and intercept top and bottom swipes.
     *
     * Also clears gesture exclusion zones when the hub is occluded or gone.
     */
    private fun updateLifecycleState() {
    private fun updateTouchHandlingState() {
        val shouldInterceptGestures = hubShowing && !(shadeShowing || anyBouncerShowing)
        if (shouldInterceptGestures) {
            lifecycleRegistry.currentState = Lifecycle.State.RESUMED
        } else {
            // Hub is either occluded or no longer showing, turn off touch handling.
            lifecycleRegistry.currentState = Lifecycle.State.STARTED

            // Clear exclusion rects if the hub is not showing or is covered, so we don't interfere
            // with back gestures when the bouncer or shade. We do this here instead of with
            // repeatOnLifecycle as repeatOnLifecycle does not run when going from RESUMED back to
            // STARTED, only when going from CREATED to STARTED.
            communalContainerView!!.systemGestureExclusionRects = emptyList()
        }
    }

+51 −17
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -42,10 +43,8 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -238,11 +237,7 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
                goToScene(CommunalScenes.Communal)

                // Bouncer is visible.
                fakeKeyguardTransitionRepository.sendTransitionSteps(
                    KeyguardState.GLANCEABLE_HUB,
                    KeyguardState.PRIMARY_BOUNCER,
                    testScope
                )
                fakeKeyguardBouncerRepository.setPrimaryShow(true)
                testableLooper.processAllMessages()

                // Touch events are not intercepted.
@@ -368,11 +363,7 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
                goToScene(CommunalScenes.Communal)

                // Bouncer is visible.
                fakeKeyguardTransitionRepository.sendTransitionSteps(
                    KeyguardState.GLANCEABLE_HUB,
                    KeyguardState.PRIMARY_BOUNCER,
                    testScope
                )
                fakeKeyguardBouncerRepository.setPrimaryShow(true)
                testableLooper.processAllMessages()

                assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
@@ -387,11 +378,7 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
                goToScene(CommunalScenes.Communal)

                // Bouncer is visible.
                fakeKeyguardTransitionRepository.sendTransitionSteps(
                    KeyguardState.GLANCEABLE_HUB,
                    KeyguardState.ALTERNATE_BOUNCER,
                    testScope
                )
                fakeKeyguardBouncerRepository.setAlternateVisible(true)
                testableLooper.processAllMessages()

                assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
@@ -452,6 +439,53 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
            }
        }

    @Test
    fun gestureExclusionZone_unsetWhenShadeOpen() =
        with(kosmos) {
            testScope.runTest {
                goToScene(CommunalScenes.Communal)

                // Shade shows up.
                shadeTestUtil.setQsExpansion(1.0f)
                testableLooper.processAllMessages()

                // Exclusion rects are unset.
                assertThat(containerView.systemGestureExclusionRects).isEmpty()
            }
        }

    @Test
    fun gestureExclusionZone_unsetWhenBouncerOpen() =
        with(kosmos) {
            testScope.runTest {
                goToScene(CommunalScenes.Communal)

                // Bouncer is visible.
                fakeKeyguardBouncerRepository.setPrimaryShow(true)
                testableLooper.processAllMessages()

                // Exclusion rects are unset.
                assertThat(containerView.systemGestureExclusionRects).isEmpty()
            }
        }

    @Test
    fun gestureExclusionZone_unsetWhenHubClosed() =
        with(kosmos) {
            testScope.runTest {
                goToScene(CommunalScenes.Communal)

                // Exclusion rect is set.
                assertThat(containerView.systemGestureExclusionRects).hasSize(1)

                // Leave the hub.
                goToScene(CommunalScenes.Blank)

                // Exclusion rect is unset.
                assertThat(containerView.systemGestureExclusionRects).isEmpty()
            }
        }

    private fun initAndAttachContainerView() {
        containerView = View(context)