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

NotificationShadeWindowViewController instead of the view itself.

Bug: 209005990
Test: new NotificationShadeWindowViewControllerTest
Change-Id: I6303fb934701a88f7ce52e5e73e8d3df5f18fed8
This commit is contained in:
Caitlin Cassidy
2021-12-28 16:53:34 +00:00
parent 2e62e0467c
commit bb7dc5afcb
5 changed files with 353 additions and 25 deletions

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -0,0 +1,238 @@
/*
* 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

View File

@@ -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