From c4b826bd575e6806772f5c9de622f2fd4d677e27 Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Tue, 7 Mar 2023 17:51:17 -0800 Subject: [PATCH] Multi-shade foundation - integration (5/5). Early foundation for the multi-shade framework. Integrates the basic framework into the current NotificationShadeWindowView. Does not do any extra work to make it work. The change is behind the DUAL_SHADE flag which is currently off. https://drive.google.com/file/d/1nmbIQuwvWZ0VqnWHxqfcpxbZqapbHtmZ/view?usp=sharing Bug: 272130181 Test: Manually tested end-to-end. When the flag is off, there is no impact on system UI behaviour. When the flag is on, dual shade appears on top of the normal shade. It even accepts input and the shades can be expanded and collapsed. Flag: DUAL_SHADE Change-Id: I0fc00d05a1fc9451ca92a53638dc3cbc7a058459 --- packages/SystemUI/res/layout/multi_shade.xml | 22 ++ .../res/layout/super_notification_shade.xml | 7 + .../multishade/ui/view/MultiShadeView.kt | 71 +++++ ...NotificationShadeWindowViewController.java | 27 +- ...tificationShadeWindowViewControllerTest.kt | 248 +++++++++-------- .../NotificationShadeWindowViewTest.java | 226 ---------------- .../shade/NotificationShadeWindowViewTest.kt | 249 ++++++++++++++++++ 7 files changed, 517 insertions(+), 333 deletions(-) create mode 100644 packages/SystemUI/res/layout/multi_shade.xml create mode 100644 packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt diff --git a/packages/SystemUI/res/layout/multi_shade.xml b/packages/SystemUI/res/layout/multi_shade.xml new file mode 100644 index 0000000000000..78ff5f03bea2d --- /dev/null +++ b/packages/SystemUI/res/layout/multi_shade.xml @@ -0,0 +1,22 @@ + + + + diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 4abc1769ab545..fe9542b7aed65 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -110,6 +110,13 @@ android:clipChildren="false" android:clipToPadding="false" /> + + multiShadeInteractorProvider, + SystemClock clock) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; mStatusBarStateController = statusBarStateController; @@ -175,6 +184,16 @@ public class NotificationShadeWindowViewController { collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), mLockscreenToDreamingTransition); + + mClock = clock; + if (ComposeFacade.INSTANCE.isComposeAvailable() + && featureFlags.isEnabled(Flags.DUAL_SHADE)) { + final ViewStub multiShadeViewStub = mView.findViewById(R.id.multi_shade_stub); + if (multiShadeViewStub != null) { + final MultiShadeView multiShadeView = (MultiShadeView) multiShadeViewStub.inflate(); + multiShadeView.init(multiShadeInteractorProvider.get(), clock); + } + } } /** @@ -269,7 +288,7 @@ public class NotificationShadeWindowViewController { mLockIconViewController.onTouchEvent( ev, () -> mService.wakeUpIfDozing( - SystemClock.uptimeMillis(), + mClock.uptimeMillis(), mView, "LOCK_ICON_TOUCH", PowerManager.WAKE_REASON_GESTURE) @@ -453,7 +472,7 @@ public class NotificationShadeWindowViewController { public void cancelCurrentTouch() { if (mTouchActive) { - final long now = SystemClock.uptimeMillis(); + final long now = mClock.uptimeMillis(); final MotionEvent event; if (mIsTrackpadGestureBackEnabled) { event = MotionEvent.obtain(mDownEvent); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 0dc2d1700f155..bdb0e7ed8d9d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -37,6 +37,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel +import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy +import com.android.systemui.multishade.data.repository.MultiShadeRepository +import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.NotificationInsetsController @@ -50,8 +53,12 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.util.mockito.any +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -65,10 +72,12 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) class NotificationShadeWindowViewControllerTest : SysuiTestCase() { + @Mock private lateinit var view: NotificationShadeWindowView @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController @Mock private lateinit var centralSurfaces: CentralSurfaces @@ -102,6 +111,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var underTest: NotificationShadeWindowViewController + private lateinit var testScope: TestScope + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -115,8 +126,12 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) .thenReturn(emptyFlow()) - val featureFlags = FakeFeatureFlags(); + val featureFlags = FakeFeatureFlags() featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false) + featureFlags.set(Flags.DUAL_SHADE, false) + + val inputProxy = MultiShadeInputProxy() + testScope = TestScope() underTest = NotificationShadeWindowViewController( lockscreenShadeTransitionController, @@ -144,6 +159,18 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, featureFlags, + { + MultiShadeInteractor( + applicationScope = testScope.backgroundScope, + repository = + MultiShadeRepository( + applicationContext = context, + inputProxy = inputProxy, + ), + inputProxy = inputProxy, + ) + }, + FakeSystemClock(), ) underTest.setupExpandedStatusBar() @@ -156,147 +183,162 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // tests need to be added to test the rest of handleDispatchTouchEvent. @Test - fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() { - underTest.setStatusBarViewController(null) + fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() = + testScope.runTest { + underTest.setStatusBarViewController(null) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) - assertThat(returnVal).isFalse() - } + assertThat(returnVal).isFalse() + } @Test - fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) - whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true) + fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) + whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev) - verify(phoneStatusBarViewController).sendTouchToView(ev) - assertThat(returnVal).isTrue() - } + verify(phoneStatusBarViewController).sendTouchToView(ev) + assertThat(returnVal).isTrue() + } @Test - fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - val downEvBelow = - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) - interactionEventHandler.handleDispatchTouchEvent(downEvBelow) + fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + val downEvBelow = + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) + interactionEventHandler.handleDispatchTouchEvent(downEvBelow) - val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0) - whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) + val nextEvent = + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0) + whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) - verify(phoneStatusBarViewController).sendTouchToView(nextEvent) - assertThat(returnVal).isTrue() - } + verify(phoneStatusBarViewController).sendTouchToView(nextEvent) + assertThat(returnVal).isTrue() + } @Test - fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) - whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) - whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) - whenever(phoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true) + fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) + whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) - verify(phoneStatusBarViewController).sendTouchToView(downEv) - assertThat(returnVal).isTrue() - } + verify(phoneStatusBarViewController).sendTouchToView(DOWN_EVENT) + assertThat(returnVal).isTrue() + } @Test - fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) - whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) - // Item we're testing - whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false) + fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) + // Item we're testing + whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isNull() - } + verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) + assertThat(returnVal).isNull() + } @Test - fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) - whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) - // Item we're testing - whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(false) + fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + // Item we're testing + whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(false) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isNull() - } + verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) + assertThat(returnVal).isNull() + } @Test - fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) - whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) - // Item we're testing - whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false) + fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) + // Item we're testing + whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isTrue() - } + verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) + assertThat(returnVal).isTrue() + } @Test - fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) - whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) - whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) + fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() = + testScope.runTest { + underTest.setStatusBarViewController(phoneStatusBarViewController) + whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) - // Down event first - interactionEventHandler.handleDispatchTouchEvent(downEv) + // Down event first + interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) - // Then another event - val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) + // Then another event + val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) - val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) + val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) - verify(phoneStatusBarViewController).sendTouchToView(nextEvent) - assertThat(returnVal).isTrue() - } + verify(phoneStatusBarViewController).sendTouchToView(nextEvent) + assertThat(returnVal).isTrue() + } @Test - fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() { - // Down event within udfpsOverlay bounds while alternateBouncer is showing - whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(downEv)).thenReturn(false) - whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() = + testScope.runTest { + // Down event within udfpsOverlay bounds while alternateBouncer is showing + whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT)) + .thenReturn(false) + whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) - // Then touch should not be intercepted - val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv) - assertThat(shouldIntercept).isFalse() - } + // Then touch should not be intercepted + val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) + assertThat(shouldIntercept).isFalse() + } @Test - fun testGetBouncerContainer() { - Mockito.clearInvocations(view) - underTest.bouncerContainer - verify(view).findViewById(R.id.keyguard_bouncer_container) - } + fun testGetBouncerContainer() = + testScope.runTest { + Mockito.clearInvocations(view) + underTest.bouncerContainer + verify(view).findViewById(R.id.keyguard_bouncer_container) + } @Test - fun testGetKeyguardMessageArea() { - underTest.keyguardMessageArea - verify(view).findViewById(R.id.keyguard_message_area) + fun testGetKeyguardMessageArea() = + testScope.runTest { + underTest.keyguardMessageArea + verify(view).findViewById(R.id.keyguard_message_area) + } + + companion object { + private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + private const val VIEW_BOTTOM = 100 } } - -private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) -private const val VIEW_BOTTOM = 100 diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java deleted file mode 100644 index 2797440ce1f18..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (C) 2017 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.shade; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import static kotlinx.coroutines.flow.FlowKt.emptyFlow; - -import android.os.SystemClock; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.MotionEvent; -import android.view.ViewGroup; - -import androidx.test.filters.SmallTest; - -import com.android.keyguard.KeyguardSecurityContainerController; -import com.android.keyguard.LockIconViewController; -import com.android.keyguard.dagger.KeyguardBouncerComponent; -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; -import com.android.systemui.classifier.FalsingCollectorFake; -import com.android.systemui.dock.DockManager; -import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; -import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; -import com.android.systemui.statusbar.DragDownHelper; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.NotificationInsetsController; -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.AmbientState; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; -import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.window.StatusBarWindowStateController; -import com.android.systemui.tuner.TunerService; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -@SmallTest -public class NotificationShadeWindowViewTest extends SysuiTestCase { - - private NotificationShadeWindowView mView; - private NotificationShadeWindowViewController mController; - - @Mock private TunerService mTunerService; - @Mock private DragDownHelper mDragDownHelper; - @Mock private SysuiStatusBarStateController mStatusBarStateController; - @Mock private ShadeController mShadeController; - @Mock private CentralSurfaces mCentralSurfaces; - @Mock private DockManager mDockManager; - @Mock private NotificationPanelViewController mNotificationPanelViewController; - @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; - @Mock private NotificationShadeDepthController mNotificationShadeDepthController; - @Mock private NotificationShadeWindowController mNotificationShadeWindowController; - @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; - @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - @Mock private StatusBarWindowStateController mStatusBarWindowStateController; - @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; - @Mock private LockIconViewController mLockIconViewController; - @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - @Mock private AmbientState mAmbientState; - @Mock private PulsingGestureListener mPulsingGestureListener; - @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel; - @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; - @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent; - @Mock private KeyguardSecurityContainerController mKeyguardSecurityContainerController; - @Mock private NotificationInsetsController mNotificationInsetsController; - @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; - @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor; - @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; - @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; - - @Captor private ArgumentCaptor - mInteractionEventHandlerCaptor; - private NotificationShadeWindowView.InteractionEventHandler mInteractionEventHandler; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mView = spy(new NotificationShadeWindowView(getContext(), null)); - when(mView.findViewById(R.id.notification_stack_scroller)) - .thenReturn(mNotificationStackScrollLayout); - - when(mView.findViewById(R.id.keyguard_bouncer_container)).thenReturn(mock(ViewGroup.class)); - when(mKeyguardBouncerComponentFactory.create(any(ViewGroup.class))).thenReturn( - mKeyguardBouncerComponent); - when(mKeyguardBouncerComponent.getSecurityContainerController()).thenReturn( - mKeyguardSecurityContainerController); - - when(mStatusBarStateController.isDozing()).thenReturn(false); - mDependency.injectTestDependency(ShadeController.class, mShadeController); - - when(mDockManager.isDocked()).thenReturn(false); - - when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition()) - .thenReturn(emptyFlow()); - - FakeFeatureFlags featureFlags = new FakeFeatureFlags(); - featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false); - mController = new NotificationShadeWindowViewController( - mLockscreenShadeTransitionController, - new FalsingCollectorFake(), - mStatusBarStateController, - mDockManager, - mNotificationShadeDepthController, - mView, - mNotificationPanelViewController, - new ShadeExpansionStateManager(), - mNotificationStackScrollLayoutController, - mStatusBarKeyguardViewManager, - mStatusBarWindowStateController, - mLockIconViewController, - mCentralSurfaces, - mNotificationShadeWindowController, - mKeyguardUnlockAnimationController, - mNotificationInsetsController, - mAmbientState, - mPulsingGestureListener, - mKeyguardBouncerViewModel, - mKeyguardBouncerComponentFactory, - mAlternateBouncerInteractor, - mUdfpsOverlayInteractor, - mKeyguardTransitionInteractor, - mPrimaryBouncerToGoneTransitionViewModel, - featureFlags - ); - mController.setupExpandedStatusBar(); - mController.setDragDownHelper(mDragDownHelper); - } - - @Test - public void testDragDownHelperCalledWhenDraggingDown() { - when(mDragDownHelper.isDraggingDown()).thenReturn(true); - long now = SystemClock.elapsedRealtime(); - MotionEvent ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0 /* x */, 0 /* y */, - 0 /* meta */); - mView.onTouchEvent(ev); - verify(mDragDownHelper).onTouchEvent(ev); - ev.recycle(); - } - - @Test - public void testInterceptTouchWhenShowingAltAuth() { - captureInteractionEventHandler(); - - // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true); - when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); - - // THEN we should intercept touch - assertTrue(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class))); - } - - @Test - public void testNoInterceptTouch() { - captureInteractionEventHandler(); - - // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); - - // THEN we shouldn't intercept touch - assertFalse(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class))); - } - - @Test - public void testHandleTouchEventWhenShowingAltAuth() { - captureInteractionEventHandler(); - - // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); - - // THEN we should handle the touch - assertTrue(mInteractionEventHandler.handleTouchEvent(mock(MotionEvent.class))); - } - - private void captureInteractionEventHandler() { - verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture()); - mInteractionEventHandler = mInteractionEventHandlerCaptor.getValue(); - - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt new file mode 100644 index 0000000000000..5d0f408a05225 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2017 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.shade + +import android.os.SystemClock +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.MotionEvent +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardSecurityContainerController +import com.android.keyguard.LockIconViewController +import com.android.keyguard.dagger.KeyguardBouncerComponent +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor +import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.dock.DockManager +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.KeyguardUnlockAnimationController +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel +import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy +import com.android.systemui.multishade.data.repository.MultiShadeRepository +import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor +import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.statusbar.DragDownHelper +import com.android.systemui.statusbar.LockscreenShadeTransitionController +import com.android.systemui.statusbar.NotificationInsetsController +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.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.statusbar.window.StatusBarWindowStateController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +@SmallTest +class NotificationShadeWindowViewTest : SysuiTestCase() { + + @Mock private lateinit var dragDownHelper: DragDownHelper + @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock private lateinit var shadeController: ShadeController + @Mock private lateinit var centralSurfaces: CentralSurfaces + @Mock private lateinit var dockManager: DockManager + @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController + @Mock private lateinit var notificationStackScrollLayout: NotificationStackScrollLayout + @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController + @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock + private lateinit var notificationStackScrollLayoutController: + NotificationStackScrollLayoutController + @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController + @Mock + private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController + @Mock private lateinit var lockIconViewController: LockIconViewController + @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController + @Mock private lateinit var ambientState: AmbientState + @Mock private lateinit var pulsingGestureListener: PulsingGestureListener + @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel + @Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory + @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent + @Mock + private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController + @Mock private lateinit var notificationInsetsController: NotificationInsetsController + @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor + @Mock + private lateinit var primaryBouncerToGoneTransitionViewModel: + PrimaryBouncerToGoneTransitionViewModel + @Captor + private lateinit var interactionEventHandlerCaptor: ArgumentCaptor + @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor + + private lateinit var underTest: NotificationShadeWindowView + private lateinit var controller: NotificationShadeWindowViewController + private lateinit var interactionEventHandler: InteractionEventHandler + private lateinit var testScope: TestScope + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + underTest = spy(NotificationShadeWindowView(context, null)) + whenever( + underTest.findViewById( + R.id.notification_stack_scroller + ) + ) + .thenReturn(notificationStackScrollLayout) + whenever(underTest.findViewById(R.id.keyguard_bouncer_container)) + .thenReturn(mock()) + whenever(keyguardBouncerComponentFactory.create(any())).thenReturn(keyguardBouncerComponent) + whenever(keyguardBouncerComponent.securityContainerController) + .thenReturn(keyguardSecurityContainerController) + whenever(statusBarStateController.isDozing).thenReturn(false) + mDependency.injectTestDependency(ShadeController::class.java, shadeController) + whenever(dockManager.isDocked).thenReturn(false) + whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) + .thenReturn(emptyFlow()) + + val featureFlags = FakeFeatureFlags() + featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false) + featureFlags.set(Flags.DUAL_SHADE, false) + val inputProxy = MultiShadeInputProxy() + testScope = TestScope() + controller = + NotificationShadeWindowViewController( + lockscreenShadeTransitionController, + FalsingCollectorFake(), + statusBarStateController, + dockManager, + notificationShadeDepthController, + underTest, + notificationPanelViewController, + ShadeExpansionStateManager(), + notificationStackScrollLayoutController, + statusBarKeyguardViewManager, + statusBarWindowStateController, + lockIconViewController, + centralSurfaces, + notificationShadeWindowController, + keyguardUnlockAnimationController, + notificationInsetsController, + ambientState, + pulsingGestureListener, + keyguardBouncerViewModel, + keyguardBouncerComponentFactory, + alternateBouncerInteractor, + udfpsOverlayInteractor, + keyguardTransitionInteractor, + primaryBouncerToGoneTransitionViewModel, + featureFlags, + { + MultiShadeInteractor( + applicationScope = testScope.backgroundScope, + repository = + MultiShadeRepository( + applicationContext = context, + inputProxy = inputProxy, + ), + inputProxy = inputProxy, + ) + }, + FakeSystemClock(), + ) + + controller.setupExpandedStatusBar() + controller.setDragDownHelper(dragDownHelper) + } + + @Test + fun testDragDownHelperCalledWhenDraggingDown() = + testScope.runTest { + whenever(dragDownHelper.isDraggingDown).thenReturn(true) + val now = SystemClock.elapsedRealtime() + val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */) + underTest.onTouchEvent(ev) + verify(dragDownHelper).onTouchEvent(ev) + ev.recycle() + } + + @Test + fun testInterceptTouchWhenShowingAltAuth() = + testScope.runTest { + captureInteractionEventHandler() + + // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept + whenever(statusBarStateController.isDozing).thenReturn(false) + whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true) + whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) + + // THEN we should intercept touch + assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isTrue() + } + + @Test + fun testNoInterceptTouch() = + testScope.runTest { + captureInteractionEventHandler() + + // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept + whenever(statusBarStateController.isDozing).thenReturn(false) + whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(false) + whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) + + // THEN we shouldn't intercept touch + assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isFalse() + } + + @Test + fun testHandleTouchEventWhenShowingAltAuth() = + testScope.runTest { + captureInteractionEventHandler() + + // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept + whenever(statusBarStateController.isDozing).thenReturn(false) + whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) + + // THEN we should handle the touch + assertThat(interactionEventHandler.handleTouchEvent(mock())).isTrue() + } + + private fun captureInteractionEventHandler() { + verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) + interactionEventHandler = interactionEventHandlerCaptor.value + } +}