diff options
7 files changed, 526 insertions, 342 deletions
diff --git a/packages/SystemUI/res/layout/multi_shade.xml b/packages/SystemUI/res/layout/multi_shade.xml new file mode 100644 index 000000000000..78ff5f03bea2 --- /dev/null +++ b/packages/SystemUI/res/layout/multi_shade.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + ~ + --> + +<com.android.systemui.multishade.ui.view.MultiShadeView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 4abc1769ab54..fe9542b7aed6 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" /> + <ViewStub + android:id="@+id/multi_shade_stub" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:inflatedId="@+id/multi_shade" + android:layout="@layout/multi_shade" /> + <com.android.systemui.biometrics.AuthRippleView android:id="@+id/auth_ripple" android:layout_width="match_parent" diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt new file mode 100644 index 000000000000..aecec39c5c07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 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.multishade.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.compose.ComposeFacade +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor +import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel +import com.android.systemui.util.time.SystemClock +import kotlinx.coroutines.launch + +/** + * View that hosts the multi-shade system and acts as glue between legacy code and the + * implementation. + */ +class MultiShadeView( + context: Context, + attrs: AttributeSet?, +) : + FrameLayout( + context, + attrs, + ) { + + fun init( + interactor: MultiShadeInteractor, + clock: SystemClock, + ) { + repeatWhenAttached { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + addView( + ComposeFacade.createMultiShadeView( + context = context, + viewModel = + MultiShadeViewModel( + viewModelScope = this, + interactor = interactor, + ), + clock = clock, + ) + ) + } + + // Here when destroyed. + removeAllViews() + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 289908136e7e..a716a6ea55fb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -23,7 +23,6 @@ import android.app.StatusBarManager; import android.media.AudioManager; import android.media.session.MediaSessionLegacyHelper; import android.os.PowerManager; -import android.os.SystemClock; import android.util.Log; import android.view.GestureDetector; import android.view.InputDevice; @@ -31,6 +30,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewStub; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AuthKeyguardMessageArea; @@ -39,8 +39,10 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.compose.ComposeFacade; import com.android.systemui.dock.DockManager; import com.android.systemui.flags.FeatureFlags; +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; @@ -49,6 +51,8 @@ import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; +import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor; +import com.android.systemui.multishade.ui.view.MultiShadeView; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationInsetsController; @@ -63,11 +67,13 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.window.StatusBarWindowStateController; +import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; import java.util.function.Consumer; import javax.inject.Inject; +import javax.inject.Provider; /** * Controller for {@link NotificationShadeWindowView}. @@ -115,6 +121,7 @@ public class NotificationShadeWindowViewController { mIsOcclusionTransitionRunning = step.getTransitionState() == TransitionState.RUNNING; }; + private final SystemClock mClock; @Inject public NotificationShadeWindowViewController( @@ -142,7 +149,9 @@ public class NotificationShadeWindowViewController { UdfpsOverlayInteractor udfpsOverlayInteractor, KeyguardTransitionInteractor keyguardTransitionInteractor, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + Provider<MultiShadeInteractor> 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 0dc2d1700f15..bdb0e7ed8d9d 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<TransitionStep>()) - 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) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController).sendTouchToView(downEv) - assertThat(returnVal).isTrue() - } + 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(DOWN_EVENT) + + 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) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isNull() - } + 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(DOWN_EVENT) + + 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) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isNull() - } + 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(DOWN_EVENT) + + 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) - - val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) - - verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) - assertThat(returnVal).isTrue() - } + 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(DOWN_EVENT) + + 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) - - // Then touch should not be intercepted - val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv) - assertThat(shouldIntercept).isFalse() - } + 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(DOWN_EVENT) + assertThat(shouldIntercept).isFalse() + } @Test - fun testGetBouncerContainer() { - Mockito.clearInvocations(view) - underTest.bouncerContainer - verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container) - } + fun testGetBouncerContainer() = + testScope.runTest { + Mockito.clearInvocations(view) + underTest.bouncerContainer + verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container) + } @Test - fun testGetKeyguardMessageArea() { - underTest.keyguardMessageArea - verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) + fun testGetKeyguardMessageArea() = + testScope.runTest { + underTest.keyguardMessageArea + verify(view).findViewById<ViewGroup>(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 2797440ce1f1..000000000000 --- 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<NotificationShadeWindowView.InteractionEventHandler> - 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 000000000000..5d0f408a0522 --- /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<InteractionEventHandler> + @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<NotificationStackScrollLayout>( + R.id.notification_stack_scroller + ) + ) + .thenReturn(notificationStackScrollLayout) + whenever(underTest.findViewById<FrameLayout>(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 + } +} |