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

Commit bb7dc5af authored by Caitlin Cassidy's avatar Caitlin Cassidy
Browse files

[Status Bar Refactor] Pass status bar view's controller to

NotificationShadeWindowViewController instead of the view itself.

Bug: 209005990
Test: new NotificationShadeWindowViewControllerTest
Change-Id: I6303fb934701a88f7ce52e5e73e8d3df5f18fed8
parent 2e62e046
Loading
Loading
Loading
Loading
+9 −19
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.phone;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;

import android.app.StatusBarManager;
import android.graphics.RectF;
import android.hardware.display.AmbientDisplayConfiguration;
import android.media.AudioManager;
import android.media.session.MediaSessionLegacyHelper;
@@ -75,7 +74,7 @@ public class NotificationShadeWindowViewController {
    private boolean mTouchCancelled;
    private boolean mExpandAnimationRunning;
    private NotificationStackScrollLayout mStackScrollLayout;
    private PhoneStatusBarView mStatusBarView;
    private PhoneStatusBarViewController mStatusBarViewController;
    private StatusBar mService;
    private NotificationShadeWindowController mNotificationShadeWindowController;
    private DragDownHelper mDragDownHelper;
@@ -86,8 +85,6 @@ public class NotificationShadeWindowViewController {
    private final NotificationPanelViewController mNotificationPanelViewController;
    private final PanelExpansionStateManager mPanelExpansionStateManager;

    // Used for determining view / touch intersection
    private final RectF mTempRect = new RectF();
    private boolean mIsTrackingBarGesture = false;

    @Inject
@@ -175,7 +172,7 @@ public class NotificationShadeWindowViewController {
        mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
            @Override
            public Boolean handleDispatchTouchEvent(MotionEvent ev) {
                if (mStatusBarView == null) {
                if (mStatusBarViewController == null) { // Fix for b/192490822
                    Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
                    return false;
                }
@@ -243,27 +240,27 @@ public class NotificationShadeWindowViewController {
                    expandingBelowNotch = true;
                }
                if (expandingBelowNotch) {
                    return mStatusBarView.dispatchTouchEvent(ev);
                    return mStatusBarViewController.sendTouchToView(ev);
                }

                if (!mIsTrackingBarGesture && isDown
                        && mNotificationPanelViewController.isFullyCollapsed()) {
                    float x = ev.getRawX();
                    float y = ev.getRawY();
                    if (isIntersecting(mStatusBarView, x, y)) {
                    if (mStatusBarViewController.touchIsWithinView(x, y)) {
                        if (mService.isSameStatusBarState(WINDOW_STATE_SHOWING)) {
                            mIsTrackingBarGesture = true;
                            return mStatusBarView.dispatchTouchEvent(ev);
                            return mStatusBarViewController.sendTouchToView(ev);
                        } else { // it's hidden or hiding, don't send to notification shade.
                            return true;
                        }
                    }
                } else if (mIsTrackingBarGesture) {
                    final boolean sendToNotification = mStatusBarView.dispatchTouchEvent(ev);
                    final boolean sendToStatusBar = mStatusBarViewController.sendTouchToView(ev);
                    if (isUp || isCancel) {
                        mIsTrackingBarGesture = false;
                    }
                    return sendToNotification;
                    return sendToStatusBar;
                }

                return null;
@@ -442,8 +439,8 @@ public class NotificationShadeWindowViewController {
        }
    }

    public void setStatusBarView(PhoneStatusBarView statusBarView) {
        mStatusBarView = statusBarView;
    public void setStatusBarViewController(PhoneStatusBarViewController statusBarViewController) {
        mStatusBarViewController = statusBarViewController;
    }

    public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
@@ -455,11 +452,4 @@ public class NotificationShadeWindowViewController {
    void setDragDownHelper(DragDownHelper dragDownHelper) {
        mDragDownHelper = dragDownHelper;
    }

    private boolean isIntersecting(View view, float x, float y) {
        int[] mTempLocation = view.getLocationOnScreen();
        mTempRect.set(mTempLocation[0], mTempLocation[1], mTempLocation[0] + view.getWidth(),
                mTempLocation[1] + view.getHeight());
        return mTempRect.contains(x, y);
    }
}
+25 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone

import android.content.res.Configuration
import android.graphics.Point
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
@@ -92,6 +93,30 @@ class PhoneStatusBarViewController private constructor(
        mView.importantForAccessibility = mode
    }

    /**
     * Sends a touch event to the status bar view.
     *
     * This is required in certain cases because the status bar view is in a separate window from
     * the rest of SystemUI, and other windows may decide that their touch should instead be treated
     * as a status bar window touch.
     */
    fun sendTouchToView(ev: MotionEvent): Boolean {
        return mView.dispatchTouchEvent(ev)
    }

    /**
     * Returns true if the given (x, y) point (in screen coordinates) is within the status bar
     * view's range and false otherwise.
     */
    fun touchIsWithinView(x: Float, y: Float): Boolean {
        val left = mView.locationOnScreen[0]
        val top = mView.locationOnScreen[1]
        return left <= x &&
                x <= left + mView.width &&
                top <= y &&
                y <= top + mView.height
    }

    class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
        override fun getViewCenter(view: View, outPoint: Point) =
            when (view.id) {
+2 −1
Original line number Diff line number Diff line
@@ -1133,7 +1133,8 @@ public class StatusBar extends CoreStartable implements
                    mStatusBarView = statusBarView;
                    mPhoneStatusBarViewController = statusBarViewController;
                    mStatusBarTransitions = statusBarTransitions;
                    mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
                    mNotificationShadeWindowViewController
                            .setStatusBarViewController(mPhoneStatusBarViewController);
                    // Ensure we re-propagate panel expansion values to the panel controller and
                    // any listeners it may have, such as PanelBar. This will also ensure we
                    // re-display the notification panel if necessary (for example, if
+238 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar.phone

import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.keyguard.LockIconViewController
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.tuner.TunerService
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever

@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
    private lateinit var mController: NotificationShadeWindowViewController

    @Mock
    private lateinit var mView: NotificationShadeWindowView
    @Mock
    private lateinit var mTunerService: TunerService
    @Mock
    private lateinit var mStatusBarStateController: SysuiStatusBarStateController
    @Mock
    private lateinit var mStatusBar: StatusBar
    @Mock
    private lateinit var mDockManager: DockManager
    @Mock
    private lateinit var mNotificationPanelViewController: NotificationPanelViewController
    @Mock
    private lateinit var mNotificationShadeDepthController: NotificationShadeDepthController
    @Mock
    private lateinit var mNotificationShadeWindowController: NotificationShadeWindowController
    @Mock
    private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
    @Mock
    private lateinit var mStatusBarKeyguardViewManager: StatusBarKeyguardViewManager
    @Mock
    private lateinit var mLockscreenShadeTransitionController: LockscreenShadeTransitionController
    @Mock
    private lateinit var mLockIconViewController: LockIconViewController
    @Mock
    private lateinit var mPhoneStatusBarViewController: PhoneStatusBarViewController

    private lateinit var mInteractionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
    private lateinit var mInteractionEventHandler: InteractionEventHandler

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        whenever(mView.bottom).thenReturn(VIEW_BOTTOM)

        mController = NotificationShadeWindowViewController(
            mLockscreenShadeTransitionController,
            FalsingCollectorFake(),
            mTunerService,
            mStatusBarStateController,
            mDockManager,
            mNotificationShadeDepthController,
            mView,
            mNotificationPanelViewController,
            PanelExpansionStateManager(),
            stackScrollLayoutController,
            mStatusBarKeyguardViewManager,
            mLockIconViewController
        )
        mController.setupExpandedStatusBar()
        mController.setService(mStatusBar, mNotificationShadeWindowController)

        mInteractionEventHandlerCaptor =
            ArgumentCaptor.forClass(InteractionEventHandler::class.java)
        verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture())
            mInteractionEventHandler = mInteractionEventHandlerCaptor.value
    }

    // Note: So far, these tests only cover interactions with the status bar view controller. More
    // tests need to be added to test the rest of handleDispatchTouchEvent.

    @Test
    fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() {
        mController.setStatusBarViewController(null)

        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)

        assertThat(returnVal).isFalse()
    }

    @Test
    fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() {
        mController.setStatusBarViewController(mPhoneStatusBarViewController)
        val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
        whenever(mPhoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)

        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(ev)

        verify(mPhoneStatusBarViewController).sendTouchToView(ev)
        assertThat(returnVal).isTrue()
    }

    @Test
    fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() {
        mController.setStatusBarViewController(mPhoneStatusBarViewController)
        val downEvBelow = MotionEvent.obtain(
            0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0
        )
        mInteractionEventHandler.handleDispatchTouchEvent(downEvBelow)

        val nextEvent = MotionEvent.obtain(
            0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0
        )
        whenever(mPhoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)

        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(nextEvent)

        verify(mPhoneStatusBarViewController).sendTouchToView(nextEvent)
        assertThat(returnVal).isTrue()
    }

    @Test
    fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() {
        mController.setStatusBarViewController(mPhoneStatusBarViewController)
        whenever(mStatusBar.isSameStatusBarState(anyInt())).thenReturn(true)
        whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
        whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
            .thenReturn(true)
        whenever(mPhoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true)

        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)

        verify(mPhoneStatusBarViewController).sendTouchToView(downEv)
        assertThat(returnVal).isTrue()
    }

    @Test
    fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() {
        mController.setStatusBarViewController(mPhoneStatusBarViewController)
        whenever(mStatusBar.isSameStatusBarState(anyInt())).thenReturn(true)
        whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
            .thenReturn(true)
        // Item we're testing
        whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(false)

        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)

        verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv)
        assertThat(returnVal).isNull()
    }

    @Test
    fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() {
        mController.setStatusBarViewController(mPhoneStatusBarViewController)
        whenever(mStatusBar.isSameStatusBarState(anyInt())).thenReturn(true)
        whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
        // Item we're testing
        whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
            .thenReturn(false)

        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)

        verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv)
        assertThat(returnVal).isNull()
    }

    @Test
    fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() {
        mController.setStatusBarViewController(mPhoneStatusBarViewController)
        whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
        whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
            .thenReturn(true)
        // Item we're testing
        whenever(mStatusBar.isSameStatusBarState(anyInt())).thenReturn(false)

        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)

        verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv)
        assertThat(returnVal).isTrue()
    }

    @Test
    fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() {
        mController.setStatusBarViewController(mPhoneStatusBarViewController)
        whenever(mStatusBar.isSameStatusBarState(anyInt())).thenReturn(true)
        whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
        whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
            .thenReturn(true)

        // Down event first
        mInteractionEventHandler.handleDispatchTouchEvent(downEv)

        // Then another event
        val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
        whenever(mPhoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)

        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(nextEvent)

        verify(mPhoneStatusBarViewController).sendTouchToView(nextEvent)
        assertThat(returnVal).isTrue()
    }
}

private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
private const val VIEW_BOTTOM = 100
+79 −5
Original line number Diff line number Diff line
@@ -77,9 +77,10 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
            val parent = FrameLayout(mContext) // add parent to keep layout params
            view = LayoutInflater.from(mContext)
                .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
            view.setLeftTopRightBottom(VIEW_LEFT, VIEW_TOP, VIEW_RIGHT, VIEW_BOTTOM)
        }

        controller = createController(view)
        controller = createAndInitController(view)
    }

    @Test
@@ -99,8 +100,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
        val view = createViewMock()
        val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
        unfoldConfig.isEnabled = true
        controller = createController(view)
        controller.init()
        controller = createAndInitController(view)

        verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
        argumentCaptor.value.onPreDraw()
@@ -108,6 +108,64 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
        verify(moveFromCenterAnimation).onViewsReady(any())
    }

    @Test
    fun touchIsWithinView_inBounds_returnsTrue() {
        val view = createViewMockWithScreenLocation()
        controller = createAndInitController(view)

        assertThat(controller.touchIsWithinView(VIEW_LEFT + 1f, VIEW_TOP + 1f)).isTrue()
    }

    @Test
    fun touchIsWithinView_onTopLeftCorner_returnsTrue() {
        val view = createViewMockWithScreenLocation()
        controller = createAndInitController(view)

        assertThat(controller.touchIsWithinView(VIEW_LEFT.toFloat(), VIEW_TOP.toFloat())).isTrue()
    }

    @Test
    fun touchIsWithinView_onBottomRightCorner_returnsTrue() {
        val view = createViewMockWithScreenLocation()
        controller = createAndInitController(view)

        assertThat(controller.touchIsWithinView(
            VIEW_RIGHT.toFloat(), VIEW_BOTTOM.toFloat())
        ).isTrue()
    }

    @Test
    fun touchIsWithinView_xTooSmall_returnsFalse() {
        val view = createViewMockWithScreenLocation()
        controller = createAndInitController(view)

        assertThat(controller.touchIsWithinView(VIEW_LEFT - 1f, VIEW_TOP + 1f)).isFalse()
    }

    @Test
    fun touchIsWithinView_xTooLarge_returnsFalse() {
        val view = createViewMockWithScreenLocation()
        controller = createAndInitController(view)

        assertThat(controller.touchIsWithinView(VIEW_RIGHT + 1f, VIEW_TOP + 1f)).isFalse()
    }

    @Test
    fun touchIsWithinView_yTooSmall_returnsFalse() {
        val view = createViewMockWithScreenLocation()
        controller = createAndInitController(view)

        assertThat(controller.touchIsWithinView(VIEW_LEFT + 1f, VIEW_TOP - 1f)).isFalse()
    }

    @Test
    fun touchIsWithinView_yTooLarge_returnsFalse() {
        val view = createViewMockWithScreenLocation()
        controller = createAndInitController(view)

        assertThat(controller.touchIsWithinView(VIEW_LEFT + 1f, VIEW_BOTTOM + 1f)).isFalse()
    }

    private fun createViewMock(): PhoneStatusBarView {
        val view = spy(view)
        val viewTreeObserver = mock(ViewTreeObserver::class.java)
@@ -116,12 +174,23 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
        return view
    }

    private fun createController(view: PhoneStatusBarView): PhoneStatusBarViewController {
    private fun createViewMockWithScreenLocation(): PhoneStatusBarView {
        val view = spy(view)
        val location = IntArray(2)
        location[0] = VIEW_LEFT
        location[1] = VIEW_TOP
        `when`(view.locationOnScreen).thenReturn(location)
        return view
    }

    private fun createAndInitController(view: PhoneStatusBarView): PhoneStatusBarViewController {
        return PhoneStatusBarViewController.Factory(
            Optional.of(sysuiUnfoldComponent),
            Optional.of(progressProvider),
            configurationController
        ).create(view, touchEventHandler)
        ).create(view, touchEventHandler).also {
            it.init()
        }
    }

    private class UnfoldConfig : UnfoldTransitionConfig {
@@ -142,3 +211,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
        }
    }
}

private const val VIEW_LEFT = 30
private const val VIEW_RIGHT = 100
private const val VIEW_TOP = 40
private const val VIEW_BOTTOM = 100