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

Commit d85cb18b authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB][Chips] Allow swipe to open shade over status bar chip.

Typically, there's nothing touchable in the status bar. So,
PhoneStatusBarView#onInterceptTouchEvent is only called once for the
DOWN event and never again. When there's no status bar chip, that
behavior won't change.

When there *is* a status bar chip, there are now touchable items in
PhoneStatusBarView. When a gesture is occuring in the chip area,
PSBV#onInterceptTouchEvent will be called for all the events in
the gesture and all of them (DOWN, MOVE, etc.) will be sent to
ShadeViewController (aka NotificationPanelViewController).

If NotificationPanelViewController decides that the gesture is a swipe,
then the event will be intercepted (#onInterceptTouchEvent returns
true). The ongoing call chip will get a CANCEL event (so the chip's
click listener won't trigger) and NPVC will take over showing the shade
expansion animation.

Fixes: 185897191
Bug: 332662551
Flag: com.android.systemui.status_bar_swipe_over_chip

Most important tests:
Test: launcher, start swipe above chip -> verify QQS
expands
Test: in app, start swipe above chip -> verify QQS expands
Test: launcher, start swipe on chip -> verify QQS expands
Test: in app, start swipe on chip -> verify QQS
expands
Test: tap on ongoing call chip -> verify the app that posted the call
notification opens
Test: tap on screen share chip -> verify stop share dialog appears
Test: atest PhoneStatusBarViewTest PhoneStatusBarViewControllerTest

Other tests, pulled from the shade CUJ list:
Test: in immersive app, swipe once -> verify status bar shows. swipe
again -> verify QQS expands (swipe with both 1 and 2 fingers)
Test: keyguard, swipe down from top -> verify QS appears (both 1 and 2
fingers)
Test: launcher, 2 finger swipe down from top -> verify QS expands
Test: launcher, 2 finger swipe down from top with finger on ongoing
call chip -> verify QS expands
Test: HUN displayed, start swipe in different part of status bar ->
verify QQS expands

Change-Id: I555eb27d8f06480fee7f57aebb9aa8ce6b64c1c7
parent 5c11f930
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -376,6 +376,16 @@ flag {
    bug: "354930838"
}

flag {
    name: "status_bar_swipe_over_chip"
    namespace: "systemui"
    description: "Allow users to swipe over the status bar chip to open the shade"
    bug: "185897191"
    metadata {
      purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "compose_bouncer"
    namespace: "systemui"
+7 −2
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.widget.LinearLayout;

import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.Dependency;
import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
@@ -217,9 +218,13 @@ public class PhoneStatusBarView extends FrameLayout {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (Flags.statusBarSwipeOverChip()) {
            return mTouchEventHandler.onInterceptTouchEvent(event);
        } else {
            mTouchEventHandler.onInterceptTouchEvent(event);
            return super.onInterceptTouchEvent(event);
        }
    }

    public void updateResources() {
        mCutoutSideNudge = getResources().getDimensionPixelSize(
+25 −17
Original line number Diff line number Diff line
@@ -23,9 +23,10 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.Flags
import com.android.systemui.Gefingerpoken
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.flags.Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.ui.view.WindowRootView
@@ -83,12 +84,14 @@ private constructor(
        statusContainer.setOnHoverListener(
            statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer)
        )
        statusContainer.setOnTouchListener(object : View.OnTouchListener {
        statusContainer.setOnTouchListener(
            object : View.OnTouchListener {
                override fun onTouch(v: View, event: MotionEvent): Boolean {
                // We want to handle only mouse events here to avoid stealing finger touches from
                // status bar which expands shade when swiped down on. We're using onTouchListener
                // instead of onClickListener as the later will lead to isClickable being set to
                // true and hence ALL touches always being intercepted. See [View.OnTouchEvent]
                    // We want to handle only mouse events here to avoid stealing finger touches
                    // from status bar which expands shade when swiped down on. See b/326097469.
                    // We're using onTouchListener instead of onClickListener as the later will lead
                    // to isClickable being set to true and hence ALL touches always being
                    // intercepted. See [View.OnTouchEvent]
                    if (event.source == InputDevice.SOURCE_MOUSE) {
                        if (event.action == MotionEvent.ACTION_UP) {
                            v.performClick()
@@ -98,7 +101,8 @@ private constructor(
                    }
                    return false
                }
        })
            }
        )

        progressProvider?.setReadyToHandleTransition(true)
        configurationController.addCallback(configurationListener)
@@ -180,8 +184,12 @@ private constructor(

    inner class PhoneStatusBarViewTouchHandler : Gefingerpoken {
        override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
            return if (Flags.statusBarSwipeOverChip()) {
                shadeViewController.handleExternalInterceptTouch(event)
            } else {
                onTouch(event)
            return false
                false
            }
        }

        override fun onTouchEvent(event: MotionEvent): Boolean {
@@ -280,7 +288,7 @@ private constructor(
    ) {
        fun create(view: PhoneStatusBarView): PhoneStatusBarViewController {
            val statusBarMoveFromCenterAnimationController =
                if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
                if (featureFlags.isEnabled(ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
                    unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController()
                } else {
                    null
+72 −27
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import android.app.StatusBarManager.WINDOW_STATE_HIDDEN
import android.app.StatusBarManager.WINDOW_STATE_HIDING
import android.app.StatusBarManager.WINDOW_STATE_SHOWING
import android.app.StatusBarManager.WINDOW_STATUS_BAR
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.InputDevice
import android.view.LayoutInflater
import android.view.MotionEvent
@@ -112,8 +114,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
    @Test
    fun onViewAttachedAndDrawn_startListeningConfigurationControllerCallback() {
        val view = createViewMock()
        val argumentCaptor = ArgumentCaptor.forClass(
                ConfigurationController.ConfigurationListener::class.java)
        val argumentCaptor =
            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            controller = createAndInitController(view)
        }
@@ -159,7 +161,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
    fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
        `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false)
        val returnVal =
            view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
            view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0))
        assertThat(returnVal).isFalse()
        verify(shadeViewController, never()).handleExternalTouch(any())
    }
@@ -169,7 +171,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
        `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
        `when`(shadeViewController.isViewEnabled).thenReturn(false)
        val returnVal =
            view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
            view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0))
        assertThat(returnVal).isTrue()
        verify(shadeViewController, never()).handleExternalTouch(any())
    }
@@ -178,7 +180,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
    fun handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
        `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
        `when`(shadeViewController.isViewEnabled).thenReturn(false)
        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 2f, 0)

        view.onTouchEvent(event)

@@ -207,6 +209,50 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
        verify(shadeViewController, never()).handleExternalTouch(any())
    }

    @Test
    @DisableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
    fun handleInterceptTouchEventFromStatusBar_shadeReturnsFalse_flagOff_viewReturnsFalse() {
        `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(false)
        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)

        val returnVal = view.onInterceptTouchEvent(event)

        assertThat(returnVal).isFalse()
    }

    @Test
    @EnableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
    fun handleInterceptTouchEventFromStatusBar_shadeReturnsFalse_flagOn_viewReturnsFalse() {
        `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(false)
        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)

        val returnVal = view.onInterceptTouchEvent(event)

        assertThat(returnVal).isFalse()
    }

    @Test
    @DisableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
    fun handleInterceptTouchEventFromStatusBar_shadeReturnsTrue_flagOff_viewReturnsFalse() {
        `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(true)
        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)

        val returnVal = view.onInterceptTouchEvent(event)

        assertThat(returnVal).isFalse()
    }

    @Test
    @EnableFlags(com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
    fun handleInterceptTouchEventFromStatusBar_shadeReturnsTrue_flagOn_viewReturnsTrue() {
        `when`(shadeViewController.handleExternalInterceptTouch(any())).thenReturn(true)
        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)

        val returnVal = view.onInterceptTouchEvent(event)

        assertThat(returnVal).isTrue()
    }

    @Test
    fun onTouch_windowHidden_centralSurfacesNotNotified() {
        val callback = getCommandQueueCallback()
@@ -244,9 +290,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
            controller = createAndInitController(view)
        }
        val statusContainer = view.requireViewById<View>(R.id.system_icons)
        statusContainer.dispatchTouchEvent(
            getActionUpEventFromSource(InputDevice.SOURCE_MOUSE)
        )
        statusContainer.dispatchTouchEvent(getActionUpEventFromSource(InputDevice.SOURCE_MOUSE))
        verify(shadeControllerImpl).animateExpandShade()
    }

@@ -257,7 +301,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
            controller = createAndInitController(view)
        }
        val statusContainer = view.requireViewById<View>(R.id.system_icons)
        val handled = statusContainer.dispatchTouchEvent(
        val handled =
            statusContainer.dispatchTouchEvent(
                getActionUpEventFromSource(InputDevice.SOURCE_TOUCHSCREEN)
            )
        assertThat(handled).isFalse()
+65 −1
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.phone
import android.content.res.Configuration
import android.graphics.Insets
import android.graphics.Rect
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.view.DisplayCutout
import android.view.DisplayShape
@@ -30,6 +32,7 @@ import android.view.View
import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP
import com.android.systemui.Gefingerpoken
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.DarkIconDispatcher
@@ -82,7 +85,8 @@ class PhoneStatusBarViewTest : SysuiTestCase() {
    }

    @Test
    fun onInterceptTouchEvent_listenerNotified() {
    @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
    fun onInterceptTouchEvent_flagOff_listenerNotified() {
        val handler = TestTouchEventHandler()
        view.setTouchEventHandler(handler)

@@ -92,6 +96,66 @@ class PhoneStatusBarViewTest : SysuiTestCase() {
        assertThat(handler.lastInterceptEvent).isEqualTo(event)
    }

    @Test
    @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
    fun onInterceptTouchEvent_flagOn_listenerNotified() {
        val handler = TestTouchEventHandler()
        view.setTouchEventHandler(handler)

        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
        view.onInterceptTouchEvent(event)

        assertThat(handler.lastInterceptEvent).isEqualTo(event)
    }

    @Test
    @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
    fun onInterceptTouchEvent_listenerReturnsFalse_flagOff_viewReturnsFalse() {
        val handler = TestTouchEventHandler()
        view.setTouchEventHandler(handler)
        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)

        handler.handleTouchReturnValue = false

        assertThat(view.onInterceptTouchEvent(event)).isFalse()
    }

    @Test
    @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
    fun onInterceptTouchEvent_listenerReturnsFalse_flagOn_viewReturnsFalse() {
        val handler = TestTouchEventHandler()
        view.setTouchEventHandler(handler)
        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)

        handler.handleTouchReturnValue = false

        assertThat(view.onInterceptTouchEvent(event)).isFalse()
    }

    @Test
    @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
    fun onInterceptTouchEvent_listenerReturnsTrue_flagOff_viewReturnsFalse() {
        val handler = TestTouchEventHandler()
        view.setTouchEventHandler(handler)
        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)

        handler.handleTouchReturnValue = true

        assertThat(view.onInterceptTouchEvent(event)).isFalse()
    }

    @Test
    @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
    fun onInterceptTouchEvent_listenerReturnsTrue_flagOn_viewReturnsTrue() {
        val handler = TestTouchEventHandler()
        view.setTouchEventHandler(handler)
        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)

        handler.handleTouchReturnValue = true

        assertThat(view.onInterceptTouchEvent(event)).isTrue()
    }

    @Test
    fun onTouchEvent_listenerReturnsTrue_viewReturnsTrue() {
        val handler = TestTouchEventHandler()