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

Commit b2f78516 authored by William Xiao's avatar William Xiao Committed by Android (Google) Code Review
Browse files

Merge changes Ic4b0695c,I89c2094f into main

* changes:
  Capture touches during hub transition, not only when fully open
  Turn off GlanceableHubContainerController when scene container is enabled
parents 29bf3d33 69a87b54
Loading
Loading
Loading
Loading
+15 −57
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -69,7 +70,6 @@ constructor(
    private val communalInteractor: CommunalInteractor,
    private val communalViewModel: CommunalViewModel,
    private val dialogFactory: SystemUIDialogFactory,
    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
    private val keyguardInteractor: KeyguardInteractor,
    private val shadeInteractor: ShadeInteractor,
    private val powerManager: PowerManager,
@@ -102,12 +102,9 @@ constructor(
    private var rightEdgeSwipeRegionWidth: Int = 0

    /**
     * True if we are currently tracking a gesture for opening the hub that started in the edge
     * swipe region.
     * True if we are currently tracking a touch intercepted by the hub, either because the hub is
     * open or being opened.
     */
    private var isTrackingOpenGesture = false

    /** True if we are currently tracking a touch on the hub while it's open. */
    private var isTrackingHubTouch = false

    /**
@@ -153,7 +150,7 @@ constructor(
    /**
     * Creates the container view containing the glanceable hub UI.
     *
     * @throws RuntimeException if [isEnabled] is false or the view is already initialized
     * @throws RuntimeException if the view is already initialized
     */
    fun initView(
        context: Context,
@@ -197,6 +194,7 @@ constructor(
    /** Override for testing. */
    @VisibleForTesting
    internal fun initView(containerView: View): View {
        SceneContainerFlag.assertInLegacyMode()
        if (communalContainerView != null) {
            throw RuntimeException("Communal view has already been initialized")
        }
@@ -227,7 +225,7 @@ constructor(

        // BouncerSwipeTouchHandler has a larger gesture area than we want, set an exclusion area so
        // the gesture area doesn't overlap with widgets.
        // TODO(b/323035776): adjust gesture areaa for portrait mode
        // TODO(b/323035776): adjust gesture area for portrait mode
        containerView.repeatWhenAttached {
            // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
            // occluded.
@@ -261,7 +259,7 @@ constructor(
        )
        collectFlow(
            containerView,
            communalInteractor.isCommunalShowing,
            communalInteractor.isCommunalVisible,
            {
                hubShowing = it
                updateTouchHandlingState()
@@ -306,6 +304,7 @@ constructor(

    /** Removes the container view from its parent. */
    fun disposeView() {
        SceneContainerFlag.assertInLegacyMode()
        communalContainerView?.let {
            (it.parent as ViewGroup).removeView(it)
            lifecycleRegistry.currentState = Lifecycle.State.CREATED
@@ -323,71 +322,30 @@ constructor(
     * to be fully in control of its own touch handling.
     */
    fun onTouchEvent(ev: MotionEvent): Boolean {
        SceneContainerFlag.assertInLegacyMode()
        return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
    }

    private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean {
        // If the hub is fully visible, send all touch events to it, other than top and bottom edge
        // swipes.
        return if (hubShowing) {
            handleHubOpenTouch(view, ev)
        } else {
            handleHubClosedTouch(view, ev)
        }
    }

    private fun handleHubOpenTouch(view: View, ev: MotionEvent): Boolean {
        val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
        val isUp = ev.actionMasked == MotionEvent.ACTION_UP
        val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL

        val hubOccluded = anyBouncerShowing || shadeShowing

        if (isDown && !hubOccluded) {
            // Only intercept down events if the hub isn't occluded by the bouncer or
            // notification shade.
            isTrackingHubTouch = true
        }

        if (isTrackingHubTouch) {
            // Tracking a touch on the hub UI itself.
            if (isUp || isCancel) {
                isTrackingHubTouch = false
            }
            dispatchTouchEvent(view, ev)
            // Return true regardless of dispatch result as some touches at the start of a
            // gesture
            // may return false from dispatchTouchEvent.
            return true
        }

        return false
    }

    private fun handleHubClosedTouch(view: View, ev: MotionEvent): Boolean {
        val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
        val isUp = ev.actionMasked == MotionEvent.ACTION_UP
        val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL

        val hubOccluded = anyBouncerShowing || shadeShowing

        if (rightEdgeSwipeRegionWidth == 0) {
            // If the edge region width has not been read yet for whatever reason, don't bother
            // intercepting touches to open the hub.
            return false
        }

        if (isDown && !hubOccluded) {
            val x = ev.rawX
            val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
            if (inOpeningSwipeRegion) {
                isTrackingOpenGesture = true
            if (inOpeningSwipeRegion || hubShowing) {
                // Steal touch events when the hub is open, or if the touch started in the opening
                // gesture region.
                isTrackingHubTouch = true
            }
        }

        if (isTrackingOpenGesture) {
        if (isTrackingHubTouch) {
            if (isUp || isCancel) {
                isTrackingOpenGesture = false
                isTrackingHubTouch = false
            }
            dispatchTouchEvent(view, ev)
            // Return true regardless of dispatch result as some touches at the start of a gesture
+8 −1
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.DragDownHelper;
@@ -357,7 +358,9 @@ public class NotificationShadeWindowViewController implements Dumpable {
                mFalsingCollector.onTouchEvent(ev);
                mPulsingWakeupGestureHandler.onTouchEvent(ev);

                if (mGlanceableHubContainerController.onTouchEvent(ev)) {
                if (!SceneContainerFlag.isEnabled()
                        && mGlanceableHubContainerController.onTouchEvent(ev)) {
                    // GlanceableHubContainerController is only used pre-flexiglass.
                    return logDownDispatch(ev, "dispatched to glanceable hub container", true);
                }
                if (mDreamingWakeupGestureHandler != null
@@ -621,6 +624,10 @@ public class NotificationShadeWindowViewController implements Dumpable {
     * The layout lives in {@link R.id.communal_ui_stub}.
     */
    public void setupCommunalHubLayout() {
        if (SceneContainerFlag.isEnabled()) {
            // GlanceableHubContainerController is only used pre-flexiglass.
            return;
        }
        collectFlow(
                mView,
                mGlanceableHubContainerController.communalAvailable(),
+32 −32
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@ package com.android.systemui.shade

import android.graphics.Rect
import android.os.PowerManager
import android.platform.test.flag.junit.FlagsParameterization
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.MotionEvent
@@ -27,6 +27,7 @@ import android.widget.FrameLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
@@ -42,16 +43,11 @@ import com.android.systemui.communal.shared.model.CommunalScenes
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.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -59,6 +55,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -71,14 +68,12 @@ import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@ExperimentalCoroutinesApi
@RunWith(ParameterizedAndroidJunit4::class)
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
class GlanceableHubContainerControllerTest : SysuiTestCase() {
    private val kosmos: Kosmos =
        testKosmos().apply {
            // UnconfinedTestDispatcher makes testing simpler due to CommunalInteractor flows using
@@ -100,10 +95,6 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
    private lateinit var communalRepository: FakeCommunalRepository
    private lateinit var underTest: GlanceableHubContainerController

    init {
        mSetFlagsRule.setFlagsParameterization(flags!!)
    }

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
@@ -127,7 +118,6 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
                    communalInteractor,
                    communalViewModel,
                    dialogFactory,
                    keyguardTransitionInteractor,
                    keyguardInteractor,
                    shadeInteractor,
                    powerManager,
@@ -170,7 +160,6 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
                        communalInteractor,
                        communalViewModel,
                        dialogFactory,
                        keyguardTransitionInteractor,
                        keyguardInteractor,
                        shadeInteractor,
                        powerManager,
@@ -215,6 +204,32 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
            }
        }

    @Test
    fun onTouchEvent_communalTransitioning_interceptsTouches() =
        with(kosmos) {
            testScope.runTest {
                // Communal is opening.
                communalRepository.setTransitionState(
                    flowOf(
                        ObservableTransitionState.Transition(
                            fromScene = CommunalScenes.Blank,
                            toScene = CommunalScenes.Communal,
                            currentScene = flowOf(CommunalScenes.Blank),
                            progress = flowOf(0.5f),
                            isInitiatedByUserInput = true,
                            isUserInputOngoing = flowOf(true)
                        )
                    )
                )
                testableLooper.processAllMessages()

                // Touch events are intercepted.
                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
                // User activity sent to PowerManager.
                verify(powerManager).userActivity(any(), any(), any())
            }
        }

    @Test
    fun onTouchEvent_communalOpen_interceptsTouches() =
        with(kosmos) {
@@ -222,7 +237,7 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
                // Communal is open.
                goToScene(CommunalScenes.Communal)

                // Touch events are intercepted outside of any gesture areas.
                // Touch events are intercepted.
                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
                // User activity sent to PowerManager.
                verify(powerManager).userActivity(any(), any(), any())
@@ -289,7 +304,6 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
                    communalInteractor,
                    communalViewModel,
                    dialogFactory,
                    keyguardTransitionInteractor,
                    keyguardInteractor,
                    shadeInteractor,
                    powerManager,
@@ -309,7 +323,6 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
                    communalInteractor,
                    communalViewModel,
                    dialogFactory,
                    keyguardTransitionInteractor,
                    keyguardInteractor,
                    shadeInteractor,
                    powerManager,
@@ -501,13 +514,6 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
    }

    private fun goToScene(scene: SceneKey) {
        if (SceneContainerFlag.isEnabled) {
            if (scene == CommunalScenes.Communal) {
                kosmos.sceneInteractor.changeScene(Scenes.Communal, "test")
            } else {
                kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "test")
            }
        }
        communalRepository.changeScene(scene)
        testableLooper.processAllMessages()
    }
@@ -536,11 +542,5 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu
            MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, CONTAINER_WIDTH.toFloat(), 0f, 0)
        private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
        private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)

        @JvmStatic
        @Parameters(name = "{0}")
        fun getParams(): List<FlagsParameterization> {
            return FlagsParameterization.allCombinationsOf().andSceneContainer()
        }
    }
}
+28 −9
Original line number Diff line number Diff line
@@ -17,12 +17,15 @@
package com.android.systemui.shade

import android.content.Context
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityContainerController
import com.android.keyguard.LegacyLockIconViewController
@@ -72,7 +75,6 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
@@ -80,12 +82,12 @@ import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.atLeast
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
@@ -93,6 +95,7 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
import java.util.Optional
import org.mockito.Mockito.`when` as whenever

@OptIn(ExperimentalCoroutinesApi::class)
@@ -152,6 +155,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) :
    private lateinit var underTest: NotificationShadeWindowViewController

    private lateinit var testScope: TestScope
    private lateinit var testableLooper: TestableLooper

    private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic

@@ -181,6 +185,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) :
        mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)

        testScope = TestScope()
        testableLooper = TestableLooper.get(this)
        falsingCollector = FalsingCollectorFake()
        fakeClock = FakeSystemClock()
        underTest =
@@ -407,6 +412,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) :
    }

    @Test
    @DisableSceneContainer
    fun handleDispatchTouchEvent_glanceableHubIntercepts_returnsTrue() {
        whenever(mGlanceableHubContainerController.onTouchEvent(DOWN_EVENT)).thenReturn(true)
        underTest.setStatusBarViewController(phoneStatusBarViewController)
@@ -559,29 +565,42 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) :
        }

    @Test
    @Ignore("b/321332798")
    @DisableSceneContainer
    fun setsUpCommunalHubLayout_whenFlagEnabled() {
        whenever(mGlanceableHubContainerController.communalAvailable())
            .thenReturn(MutableStateFlow(true))

        val mockCommunalView = mock(View::class.java)
        val communalView = View(context)
        whenever(mGlanceableHubContainerController.initView(any<Context>()))
                .thenReturn(mockCommunalView)
            .thenReturn(communalView)

        val mockCommunalPlaceholder = mock(View::class.java)
        val fakeViewIndex = 20
        whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
        whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
        whenever(view.context).thenReturn(context)
        whenever(view.viewTreeObserver).thenReturn(mock(ViewTreeObserver::class.java))

        underTest.setupCommunalHubLayout()

        // Communal view added as a child of the container at the proper index, the stub is removed.
        verify(view).removeView(mockCommunalPlaceholder)
        verify(view).addView(eq(mockCommunalView), eq(fakeViewIndex))
        // Simluate attaching the view so flow collection starts.
        val onAttachStateChangeListenerArgumentCaptor = ArgumentCaptor.forClass(
            View.OnAttachStateChangeListener::class.java
        )
        verify(view, atLeast(1)).addOnAttachStateChangeListener(
            onAttachStateChangeListenerArgumentCaptor.capture()
        )
        for (listener in onAttachStateChangeListenerArgumentCaptor.allValues) {
            listener.onViewAttachedToWindow(view)
        }
        testableLooper.processAllMessages()

        // Communal view added as a child of the container at the proper index.
        verify(view).addView(eq(communalView), eq(fakeViewIndex))
    }

    @Test
    @RequiresFlagsDisabled(Flags.FLAG_COMMUNAL_HUB)
    fun doesNotSetupCommunalHubLayout_whenFlagDisabled() {
        whenever(mGlanceableHubContainerController.communalAvailable())
                .thenReturn(MutableStateFlow(false))
+1 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ class FakeCommunalRepository(
) : CommunalRepository {
    override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
        this.currentScene.value = toScene
        this._transitionState.value = flowOf(ObservableTransitionState.Idle(toScene))
    }

    private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default)