Loading packages/SystemUI/res/layout/multi_shade.xml 0 → 100644 +22 −0 Original line number Diff line number Diff line <?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" /> packages/SystemUI/res/layout/super_notification_shade.xml +7 −0 Original line number Diff line number Diff line Loading @@ -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" Loading packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * 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() } } } } packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +23 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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}. Loading Loading @@ -115,6 +121,7 @@ public class NotificationShadeWindowViewController { mIsOcclusionTransitionRunning = step.getTransitionState() == TransitionState.RUNNING; }; private final SystemClock mClock; @Inject public NotificationShadeWindowViewController( Loading Loading @@ -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; Loading Loading @@ -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); } } } /** Loading Loading @@ -269,7 +288,7 @@ public class NotificationShadeWindowViewController { mLockIconViewController.onTouchEvent( ev, () -> mService.wakeUpIfDozing( SystemClock.uptimeMillis(), mClock.uptimeMillis(), mView, "LOCK_ICON_TOUCH", PowerManager.WAKE_REASON_GESTURE) Loading Loading @@ -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); Loading packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +154 −112 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -102,6 +111,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var underTest: NotificationShadeWindowViewController private lateinit var testScope: TestScope @Before fun setUp() { MockitoAnnotations.initMocks(this) Loading @@ -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, Loading Loading @@ -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() Loading @@ -156,16 +183,18 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // tests need to be added to test the rest of handleDispatchTouchEvent. @Test fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() { fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() = testScope.runTest { underTest.setStatusBarViewController(null) val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) assertThat(returnVal).isFalse() } @Test fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() { 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) Loading @@ -177,13 +206,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() { 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) 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) Loading @@ -193,22 +224,24 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() { 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(downEv)).thenReturn(true) whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true) val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) verify(phoneStatusBarViewController).sendTouchToView(downEv) verify(phoneStatusBarViewController).sendTouchToView(DOWN_EVENT) assertThat(returnVal).isTrue() } @Test fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() { fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() = testScope.runTest { underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) Loading @@ -216,14 +249,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // 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) verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) assertThat(returnVal).isNull() } @Test fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() { fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() = testScope.runTest { underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) Loading @@ -231,14 +265,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) .thenReturn(false) val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) assertThat(returnVal).isNull() } @Test fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() { fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() = testScope.runTest { underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) Loading @@ -246,14 +281,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // 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) verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) assertThat(returnVal).isTrue() } @Test fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() { fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() = testScope.runTest { underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) Loading @@ -261,7 +297,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { .thenReturn(true) // Down event first interactionEventHandler.handleDispatchTouchEvent(downEv) interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) // Then another event val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) Loading @@ -274,29 +310,35 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() { fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() = testScope.runTest { // Down event within udfpsOverlay bounds while alternateBouncer is showing whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(downEv)).thenReturn(false) whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT)) .thenReturn(false) whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) // Then touch should not be intercepted val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv) val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) assertThat(shouldIntercept).isFalse() } @Test fun testGetBouncerContainer() { fun testGetBouncerContainer() = testScope.runTest { Mockito.clearInvocations(view) underTest.bouncerContainer verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container) } @Test fun testGetKeyguardMessageArea() { fun testGetKeyguardMessageArea() = testScope.runTest { underTest.keyguardMessageArea verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) } } private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) companion object { private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) private const val VIEW_BOTTOM = 100 } } Loading
packages/SystemUI/res/layout/multi_shade.xml 0 → 100644 +22 −0 Original line number Diff line number Diff line <?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" />
packages/SystemUI/res/layout/super_notification_shade.xml +7 −0 Original line number Diff line number Diff line Loading @@ -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" Loading
packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * 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() } } } }
packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +23 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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}. Loading Loading @@ -115,6 +121,7 @@ public class NotificationShadeWindowViewController { mIsOcclusionTransitionRunning = step.getTransitionState() == TransitionState.RUNNING; }; private final SystemClock mClock; @Inject public NotificationShadeWindowViewController( Loading Loading @@ -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; Loading Loading @@ -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); } } } /** Loading Loading @@ -269,7 +288,7 @@ public class NotificationShadeWindowViewController { mLockIconViewController.onTouchEvent( ev, () -> mService.wakeUpIfDozing( SystemClock.uptimeMillis(), mClock.uptimeMillis(), mView, "LOCK_ICON_TOUCH", PowerManager.WAKE_REASON_GESTURE) Loading Loading @@ -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); Loading
packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +154 −112 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -102,6 +111,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var underTest: NotificationShadeWindowViewController private lateinit var testScope: TestScope @Before fun setUp() { MockitoAnnotations.initMocks(this) Loading @@ -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, Loading Loading @@ -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() Loading @@ -156,16 +183,18 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // tests need to be added to test the rest of handleDispatchTouchEvent. @Test fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() { fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() = testScope.runTest { underTest.setStatusBarViewController(null) val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) assertThat(returnVal).isFalse() } @Test fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() { 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) Loading @@ -177,13 +206,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() { 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) 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) Loading @@ -193,22 +224,24 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() { 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(downEv)).thenReturn(true) whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true) val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) verify(phoneStatusBarViewController).sendTouchToView(downEv) verify(phoneStatusBarViewController).sendTouchToView(DOWN_EVENT) assertThat(returnVal).isTrue() } @Test fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() { fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() = testScope.runTest { underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) Loading @@ -216,14 +249,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // 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) verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) assertThat(returnVal).isNull() } @Test fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() { fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() = testScope.runTest { underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) Loading @@ -231,14 +265,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) .thenReturn(false) val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) assertThat(returnVal).isNull() } @Test fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() { fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() = testScope.runTest { underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) Loading @@ -246,14 +281,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // 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) verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) assertThat(returnVal).isTrue() } @Test fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() { fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() = testScope.runTest { underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) Loading @@ -261,7 +297,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { .thenReturn(true) // Down event first interactionEventHandler.handleDispatchTouchEvent(downEv) interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) // Then another event val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) Loading @@ -274,29 +310,35 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() { fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() = testScope.runTest { // Down event within udfpsOverlay bounds while alternateBouncer is showing whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(downEv)).thenReturn(false) whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT)) .thenReturn(false) whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) // Then touch should not be intercepted val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv) val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) assertThat(shouldIntercept).isFalse() } @Test fun testGetBouncerContainer() { fun testGetBouncerContainer() = testScope.runTest { Mockito.clearInvocations(view) underTest.bouncerContainer verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container) } @Test fun testGetKeyguardMessageArea() { fun testGetKeyguardMessageArea() = testScope.runTest { underTest.keyguardMessageArea verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) } } private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) companion object { private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) private const val VIEW_BOTTOM = 100 } }