1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.shade 18 19 import android.content.Context 20 import android.platform.test.annotations.RequiresFlagsDisabled 21 import android.platform.test.flag.junit.FlagsParameterization 22 import android.testing.TestableLooper 23 import android.testing.TestableLooper.RunWithLooper 24 import android.view.KeyEvent 25 import android.view.MotionEvent 26 import android.view.View 27 import android.view.ViewGroup 28 import android.view.ViewTreeObserver 29 import androidx.test.filters.SmallTest 30 import com.android.keyguard.KeyguardSecurityContainerController 31 import com.android.keyguard.LegacyLockIconViewController 32 import com.android.keyguard.dagger.KeyguardBouncerComponent 33 import com.android.systemui.Flags 34 import com.android.systemui.SysuiTestCase 35 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor 36 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor 37 import com.android.systemui.bouncer.ui.binder.BouncerViewBinder 38 import com.android.systemui.classifier.FalsingCollectorFake 39 import com.android.systemui.dock.DockManager 40 import com.android.systemui.dump.DumpManager 41 import com.android.systemui.flags.DisableSceneContainer 42 import com.android.systemui.flags.FakeFeatureFlagsClassic 43 import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED 44 import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION 45 import com.android.systemui.flags.andSceneContainer 46 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler 47 import com.android.systemui.keyguard.KeyguardUnlockAnimationController 48 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 49 import com.android.systemui.keyguard.shared.model.Edge 50 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING 51 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN 52 import com.android.systemui.keyguard.shared.model.TransitionStep 53 import com.android.systemui.res.R 54 import com.android.systemui.scene.shared.flag.SceneContainerFlag 55 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler 56 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor 57 import com.android.systemui.statusbar.DragDownHelper 58 import com.android.systemui.statusbar.LockscreenShadeTransitionController 59 import com.android.systemui.statusbar.NotificationInsetsController 60 import com.android.systemui.statusbar.NotificationShadeDepthController 61 import com.android.systemui.statusbar.NotificationShadeWindowController 62 import com.android.systemui.statusbar.SysuiStatusBarStateController 63 import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository 64 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor 65 import com.android.systemui.statusbar.notification.stack.AmbientState 66 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController 67 import com.android.systemui.statusbar.phone.CentralSurfaces 68 import com.android.systemui.statusbar.phone.DozeScrimController 69 import com.android.systemui.statusbar.phone.DozeServiceHost 70 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController 71 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager 72 import com.android.systemui.statusbar.window.StatusBarWindowStateController 73 import com.android.systemui.unfold.SysUIUnfoldComponent 74 import com.android.systemui.unfold.UnfoldTransitionProgressProvider 75 import com.android.systemui.user.domain.interactor.SelectedUserInteractor 76 import com.android.systemui.util.mockito.any 77 import com.android.systemui.util.mockito.eq 78 import com.android.systemui.util.time.FakeSystemClock 79 import com.google.common.truth.Truth.assertThat 80 import kotlinx.coroutines.ExperimentalCoroutinesApi 81 import kotlinx.coroutines.flow.MutableStateFlow 82 import kotlinx.coroutines.flow.emptyFlow 83 import kotlinx.coroutines.test.TestScope 84 import kotlinx.coroutines.test.runTest 85 import org.junit.Assert.assertEquals 86 import org.junit.Before 87 import org.junit.Test 88 import org.junit.runner.RunWith 89 import org.mockito.Answers 90 import org.mockito.ArgumentCaptor 91 import org.mockito.Mock 92 import org.mockito.Mockito.anyFloat 93 import org.mockito.Mockito.atLeast 94 import org.mockito.Mockito.mock 95 import org.mockito.Mockito.never 96 import org.mockito.Mockito.times 97 import org.mockito.Mockito.verify 98 import org.mockito.MockitoAnnotations 99 import platform.test.runner.parameterized.ParameterizedAndroidJunit4 100 import platform.test.runner.parameterized.Parameters 101 import java.util.Optional 102 import org.mockito.Mockito.`when` as whenever 103 104 @OptIn(ExperimentalCoroutinesApi::class) 105 @SmallTest 106 @RunWith(ParameterizedAndroidJunit4::class) 107 @RunWithLooper(setAsMainLooper = true) 108 class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : SysuiTestCase() { 109 110 @Mock private lateinit var view: NotificationShadeWindowView 111 @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController 112 @Mock private lateinit var centralSurfaces: CentralSurfaces 113 @Mock private lateinit var dozeServiceHost: DozeServiceHost 114 @Mock private lateinit var dozeScrimController: DozeScrimController 115 @Mock private lateinit var dockManager: DockManager 116 @Mock private lateinit var shadeViewController: ShadeViewController 117 @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor 118 @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController 119 @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController 120 @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController 121 @Mock private lateinit var shadeLogger: ShadeLogger 122 @Mock private lateinit var dumpManager: DumpManager 123 @Mock private lateinit var ambientState: AmbientState 124 @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController 125 @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager 126 @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController 127 @Mock private lateinit var quickSettingsController: QuickSettingsControllerImpl 128 @Mock 129 private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController 130 @Mock private lateinit var lockIconViewController: LegacyLockIconViewController 131 @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController 132 @Mock private lateinit var pulsingGestureListener: PulsingGestureListener 133 @Mock 134 private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener 135 @Mock private lateinit var notificationInsetsController: NotificationInsetsController 136 @Mock private lateinit var mGlanceableHubContainerController: GlanceableHubContainerController 137 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 138 private lateinit var sysUiUnfoldComponent: SysUIUnfoldComponent 139 @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory 140 @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent 141 @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController 142 @Mock 143 private lateinit var unfoldTransitionProgressProvider: 144 Optional<UnfoldTransitionProgressProvider> 145 @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor 146 @Mock lateinit var dragDownHelper: DragDownHelper 147 @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor 148 @Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler 149 @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor 150 @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor 151 private val notificationLaunchAnimationRepository = NotificationLaunchAnimationRepository() 152 private val notificationLaunchAnimationInteractor = 153 NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository) 154 155 private lateinit var falsingCollector: FalsingCollectorFake 156 private lateinit var fakeClock: FakeSystemClock 157 private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler> 158 private lateinit var interactionEventHandler: InteractionEventHandler 159 160 private lateinit var underTest: NotificationShadeWindowViewController 161 162 private lateinit var testScope: TestScope 163 private lateinit var testableLooper: TestableLooper 164 165 private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic 166 167 init { 168 mSetFlagsRule.setFlagsParameterization(flags) 169 } 170 171 @Before setUpnull172 fun setUp() { 173 MockitoAnnotations.initMocks(this) 174 whenever(view.bottom).thenReturn(VIEW_BOTTOM) 175 whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container)) 176 .thenReturn(mock(ViewGroup::class.java)) 177 whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java))) 178 .thenReturn(keyguardBouncerComponent) 179 whenever(keyguardBouncerComponent.securityContainerController) 180 .thenReturn(keyguardSecurityContainerController) 181 whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING))) 182 .thenReturn(emptyFlow<TransitionStep>()) 183 184 featureFlagsClassic = FakeFeatureFlagsClassic() 185 featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) 186 featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) 187 if (!SceneContainerFlag.isEnabled) { 188 mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) 189 } 190 mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES) 191 192 testScope = TestScope() 193 testableLooper = TestableLooper.get(this) 194 falsingCollector = FalsingCollectorFake() 195 fakeClock = FakeSystemClock() 196 underTest = 197 NotificationShadeWindowViewController( 198 lockscreenShadeTransitionController, 199 falsingCollector, 200 sysuiStatusBarStateController, 201 dockManager, 202 notificationShadeDepthController, 203 view, 204 shadeViewController, 205 panelExpansionInteractor, 206 ShadeExpansionStateManager(), 207 stackScrollLayoutController, 208 statusBarKeyguardViewManager, 209 statusBarWindowStateController, 210 lockIconViewController, 211 centralSurfaces, 212 dozeServiceHost, 213 dozeScrimController, 214 notificationShadeWindowController, 215 unfoldTransitionProgressProvider, 216 Optional.of(sysUiUnfoldComponent), 217 keyguardUnlockAnimationController, 218 notificationInsetsController, 219 ambientState, 220 shadeLogger, 221 dumpManager, 222 pulsingGestureListener, 223 mLockscreenHostedDreamGestureListener, 224 keyguardTransitionInteractor, 225 mGlanceableHubContainerController, 226 notificationLaunchAnimationInteractor, 227 featureFlagsClassic, 228 fakeClock, 229 sysUIKeyEventHandler, 230 quickSettingsController, 231 primaryBouncerInteractor, 232 alternateBouncerInteractor, 233 mock(BouncerViewBinder::class.java) 234 ) 235 underTest.setupExpandedStatusBar() 236 underTest.setDragDownHelper(dragDownHelper) 237 238 interactionEventHandlerCaptor = ArgumentCaptor.forClass(InteractionEventHandler::class.java) 239 verify(view).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) 240 interactionEventHandler = interactionEventHandlerCaptor.value 241 } 242 243 // Note: So far, these tests only cover interactions with the status bar view controller. More 244 // tests need to be added to test the rest of handleDispatchTouchEvent. 245 246 @Test handleDispatchTouchEvent_nullStatusBarViewController_returnsFalsenull247 fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() = 248 testScope.runTest { 249 underTest.setStatusBarViewController(null) 250 251 val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) 252 253 assertThat(returnVal).isFalse() 254 } 255 256 @Test handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSbnull257 fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() = 258 testScope.runTest { 259 underTest.setStatusBarViewController(phoneStatusBarViewController) 260 val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) 261 whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true) 262 263 val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev) 264 265 verify(phoneStatusBarViewController).sendTouchToView(ev) 266 assertThat(returnVal).isTrue() 267 } 268 269 @Test handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSbnull270 fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() = 271 testScope.runTest { 272 underTest.setStatusBarViewController(phoneStatusBarViewController) 273 val downEvBelow = 274 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) 275 interactionEventHandler.handleDispatchTouchEvent(downEvBelow) 276 277 val nextEvent = 278 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0) 279 whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) 280 281 val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) 282 283 verify(phoneStatusBarViewController).sendTouchToView(nextEvent) 284 assertThat(returnVal).isTrue() 285 } 286 287 @Test handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSbnull288 fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() = 289 testScope.runTest { 290 underTest.setStatusBarViewController(phoneStatusBarViewController) 291 whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) 292 whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) 293 whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) 294 .thenReturn(true) 295 whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true) 296 297 val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) 298 299 verify(phoneStatusBarViewController).sendTouchToView(DOWN_EVENT) 300 assertThat(returnVal).isTrue() 301 } 302 303 @Test handleDispatchTouchEvent_panelNotCollapsed_returnsNullnull304 fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() = 305 testScope.runTest { 306 underTest.setStatusBarViewController(phoneStatusBarViewController) 307 whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) 308 whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) 309 .thenReturn(true) 310 // Item we're testing 311 whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(false) 312 313 val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) 314 315 verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) 316 assertThat(returnVal).isNull() 317 } 318 319 @Test handleDispatchTouchEvent_touchNotInSbBounds_returnsNullnull320 fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() = 321 testScope.runTest { 322 underTest.setStatusBarViewController(phoneStatusBarViewController) 323 whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) 324 whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) 325 // Item we're testing 326 whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) 327 .thenReturn(false) 328 329 val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) 330 331 verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) 332 assertThat(returnVal).isNull() 333 } 334 335 @Test handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTruenull336 fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() = 337 testScope.runTest { 338 underTest.setStatusBarViewController(phoneStatusBarViewController) 339 whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) 340 whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) 341 .thenReturn(true) 342 // Item we're testing 343 whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false) 344 345 val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) 346 347 verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT) 348 assertThat(returnVal).isTrue() 349 } 350 351 @Test handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSbnull352 fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() = 353 testScope.runTest { 354 underTest.setStatusBarViewController(phoneStatusBarViewController) 355 whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) 356 whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) 357 whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) 358 .thenReturn(true) 359 360 // Down event first 361 interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) 362 363 // Then another event 364 val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) 365 whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) 366 367 val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) 368 369 verify(phoneStatusBarViewController).sendTouchToView(nextEvent) 370 assertThat(returnVal).isTrue() 371 } 372 373 @Test handleDispatchTouchEvent_launchAnimationRunningTimesOutnull374 fun handleDispatchTouchEvent_launchAnimationRunningTimesOut() = 375 testScope.runTest { 376 // GIVEN touch dispatcher in a state that returns true 377 underTest.setStatusBarViewController(phoneStatusBarViewController) 378 whenever(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) 379 .thenReturn(true) 380 assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue() 381 382 // WHEN launch animation is running for 2 seconds 383 fakeClock.setUptimeMillis(10000) 384 underTest.setExpandAnimationRunning(true) 385 fakeClock.advanceTime(2000) 386 387 // THEN touch is ignored 388 assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isFalse() 389 390 // WHEN Launch animation is running for 6 seconds 391 fakeClock.advanceTime(4000) 392 393 // THEN move is ignored, down is handled, and window is notified 394 assertThat(interactionEventHandler.handleDispatchTouchEvent(MOVE_EVENT)).isFalse() 395 assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue() 396 verify(notificationShadeWindowController).setLaunchingActivity(false) 397 } 398 399 @Test 400 @DisableSceneContainer handleDispatchTouchEvent_nsslMigrationOff_userActivity_not_callednull401 fun handleDispatchTouchEvent_nsslMigrationOff_userActivity_not_called() { 402 mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) 403 underTest.setStatusBarViewController(phoneStatusBarViewController) 404 405 interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) 406 407 verify(centralSurfaces, times(0)).userActivity() 408 } 409 410 @Test handleDispatchTouchEvent_nsslMigrationOn_userActivitynull411 fun handleDispatchTouchEvent_nsslMigrationOn_userActivity() { 412 enableMigrateClocksFlag() 413 underTest.setStatusBarViewController(phoneStatusBarViewController) 414 415 interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) 416 417 verify(centralSurfaces).userActivity() 418 } 419 420 @Test 421 @DisableSceneContainer handleDispatchTouchEvent_glanceableHubIntercepts_returnsTruenull422 fun handleDispatchTouchEvent_glanceableHubIntercepts_returnsTrue() { 423 whenever(mGlanceableHubContainerController.onTouchEvent(DOWN_EVENT)).thenReturn(true) 424 underTest.setStatusBarViewController(phoneStatusBarViewController) 425 426 val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) 427 428 assertThat(returnVal).isTrue() 429 } 430 431 @Test shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldInterceptnull432 fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() { 433 // down event should be intercepted by keyguardViewManager 434 whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) 435 .thenReturn(true) 436 437 // Then touch should not be intercepted 438 val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) 439 assertThat(shouldIntercept).isTrue() 440 } 441 442 @Test shouldInterceptTouchEvent_dozing_touchInLockIconArea_touchNotInterceptednull443 fun shouldInterceptTouchEvent_dozing_touchInLockIconArea_touchNotIntercepted() { 444 // GIVEN dozing 445 whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) 446 // AND alternate bouncer doesn't want the touch 447 whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) 448 .thenReturn(false) 449 // AND quick settings controller doesn't want it 450 whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) 451 .thenReturn(false) 452 // AND the lock icon wants the touch 453 whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true) 454 455 enableMigrateClocksFlag() 456 457 // THEN touch should NOT be intercepted by NotificationShade 458 assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse() 459 } 460 461 @Test shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchInterceptednull462 fun shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchIntercepted() { 463 // GIVEN dozing 464 whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) 465 // AND alternate bouncer doesn't want the touch 466 whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) 467 .thenReturn(false) 468 // AND the lock icon does NOT want the touch 469 whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false) 470 // AND quick settings controller doesn't want it 471 whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) 472 .thenReturn(false) 473 474 enableMigrateClocksFlag() 475 476 // THEN touch should be intercepted by NotificationShade 477 assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() 478 } 479 480 @Test shouldInterceptTouchEvent_dozing_touchInStatusBar_touchInterceptednull481 fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() { 482 // GIVEN dozing 483 whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) 484 // AND alternate bouncer doesn't want the touch 485 whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) 486 .thenReturn(false) 487 // AND the lock icon does NOT want the touch 488 whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false) 489 // AND quick settings controller DOES want it 490 whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) 491 .thenReturn(true) 492 493 enableMigrateClocksFlag() 494 495 // THEN touch should be intercepted by NotificationShade 496 assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() 497 } 498 499 @Test shouldInterceptTouchEvent_dozingAndPulsing_touchInterceptednull500 fun shouldInterceptTouchEvent_dozingAndPulsing_touchIntercepted() { 501 // GIVEN dozing 502 whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) 503 // AND pulsing 504 whenever(dozeServiceHost.isPulsing()).thenReturn(true) 505 // AND status bar doesn't want it 506 whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) 507 .thenReturn(false) 508 // AND shade is not fully expanded (mock is false by default) 509 // AND the lock icon does NOT want the touch 510 whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false) 511 // AND quick settings controller DOES want it 512 whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) 513 .thenReturn(true) 514 // AND bouncer is not showing 515 whenever(centralSurfaces.isBouncerShowing()).thenReturn(false) 516 // AND panel view controller wants it 517 whenever(shadeViewController.handleExternalInterceptTouch(DOWN_EVENT)) 518 .thenReturn(true) 519 520 enableMigrateClocksFlag() 521 522 // THEN touch should be intercepted by NotificationShade 523 assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() 524 } 525 526 @Test handleExternalTouch_intercepted_sendsOnTouchnull527 fun handleExternalTouch_intercepted_sendsOnTouch() { 528 // Accept dispatch and also intercept. 529 whenever(view.dispatchTouchEvent(any())).thenReturn(true) 530 whenever(view.onInterceptTouchEvent(any())).thenReturn(true) 531 532 underTest.handleExternalTouch(DOWN_EVENT) 533 underTest.handleExternalTouch(MOVE_EVENT) 534 535 // Once intercepted, both events are sent to the view. 536 verify(view).onTouchEvent(DOWN_EVENT) 537 verify(view).onTouchEvent(MOVE_EVENT) 538 } 539 540 @Test handleExternalTouch_notDispatched_interceptNotCallednull541 fun handleExternalTouch_notDispatched_interceptNotCalled() { 542 // Don't accept dispatch 543 whenever(view.dispatchTouchEvent(any())).thenReturn(false) 544 545 underTest.handleExternalTouch(DOWN_EVENT) 546 547 // Interception is not offered. 548 verify(view, never()).onInterceptTouchEvent(any()) 549 } 550 551 @Test handleExternalTouch_notIntercepted_onTouchNotSentnull552 fun handleExternalTouch_notIntercepted_onTouchNotSent() { 553 // Accept dispatch, but don't dispatch 554 whenever(view.dispatchTouchEvent(any())).thenReturn(true) 555 whenever(view.onInterceptTouchEvent(any())).thenReturn(false) 556 557 underTest.handleExternalTouch(DOWN_EVENT) 558 underTest.handleExternalTouch(MOVE_EVENT) 559 560 // Interception offered for both events, but onTouchEvent is never called. 561 verify(view).onInterceptTouchEvent(DOWN_EVENT) 562 verify(view).onInterceptTouchEvent(MOVE_EVENT) 563 verify(view, never()).onTouchEvent(any()) 564 } 565 566 @Test testGetKeyguardMessageAreanull567 fun testGetKeyguardMessageArea() = 568 testScope.runTest { 569 underTest.keyguardMessageArea 570 verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) 571 } 572 573 @Test 574 @DisableSceneContainer setsUpCommunalHubLayout_whenFlagEnablednull575 fun setsUpCommunalHubLayout_whenFlagEnabled() { 576 whenever(mGlanceableHubContainerController.communalAvailable()) 577 .thenReturn(MutableStateFlow(true)) 578 579 val communalView = View(context) 580 whenever(mGlanceableHubContainerController.initView(any<Context>())) 581 .thenReturn(communalView) 582 583 val mockCommunalPlaceholder = mock(View::class.java) 584 val fakeViewIndex = 20 585 whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder) 586 whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex) 587 whenever(view.context).thenReturn(context) 588 whenever(view.viewTreeObserver).thenReturn(mock(ViewTreeObserver::class.java)) 589 590 underTest.setupCommunalHubLayout() 591 592 // Simluate attaching the view so flow collection starts. 593 val onAttachStateChangeListenerArgumentCaptor = ArgumentCaptor.forClass( 594 View.OnAttachStateChangeListener::class.java 595 ) 596 verify(view, atLeast(1)).addOnAttachStateChangeListener( 597 onAttachStateChangeListenerArgumentCaptor.capture() 598 ) 599 for (listener in onAttachStateChangeListenerArgumentCaptor.allValues) { 600 listener.onViewAttachedToWindow(view) 601 } 602 testableLooper.processAllMessages() 603 604 // Communal view added as a child of the container at the proper index. 605 verify(view).addView(eq(communalView), eq(fakeViewIndex)) 606 } 607 608 @Test 609 @RequiresFlagsDisabled(Flags.FLAG_COMMUNAL_HUB) doesNotSetupCommunalHubLayout_whenFlagDisablednull610 fun doesNotSetupCommunalHubLayout_whenFlagDisabled() { 611 whenever(mGlanceableHubContainerController.communalAvailable()) 612 .thenReturn(MutableStateFlow(false)) 613 614 val mockCommunalPlaceholder = mock(View::class.java) 615 val fakeViewIndex = 20 616 whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder) 617 whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex) 618 whenever(view.context).thenReturn(context) 619 620 underTest.setupCommunalHubLayout() 621 622 // No adding of views occurs. 623 verify(view, times(0)).addView(any(), eq(fakeViewIndex)) 624 } 625 626 @Test forwardsDispatchKeyEventnull627 fun forwardsDispatchKeyEvent() { 628 val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B) 629 interactionEventHandler.dispatchKeyEvent(keyEvent) 630 verify(sysUIKeyEventHandler).dispatchKeyEvent(keyEvent) 631 } 632 633 @Test forwardsDispatchKeyEventPreImenull634 fun forwardsDispatchKeyEventPreIme() { 635 val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B) 636 interactionEventHandler.dispatchKeyEventPreIme(keyEvent) 637 verify(sysUIKeyEventHandler).dispatchKeyEventPreIme(keyEvent) 638 } 639 640 @Test forwardsInterceptMediaKeynull641 fun forwardsInterceptMediaKey() { 642 val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP) 643 interactionEventHandler.interceptMediaKey(keyEvent) 644 verify(sysUIKeyEventHandler).interceptMediaKey(keyEvent) 645 } 646 647 @Test forwardsCollectKeyEventnull648 fun forwardsCollectKeyEvent() { 649 val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A) 650 interactionEventHandler.collectKeyEvent(keyEvent) 651 assertEquals(keyEvent, falsingCollector.lastKeyEvent) 652 } 653 654 @Test cancelCurrentTouch_callsDragDownHelpernull655 fun cancelCurrentTouch_callsDragDownHelper() { 656 enableMigrateClocksFlag() 657 underTest.cancelCurrentTouch() 658 659 verify(dragDownHelper).stopDragging() 660 } 661 enableMigrateClocksFlagnull662 private fun enableMigrateClocksFlag() { 663 if (!Flags.migrateClocksToBlueprint()) { 664 mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) 665 } 666 } 667 668 companion object { 669 private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) 670 private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) 671 private const val VIEW_BOTTOM = 100 672 673 @JvmStatic 674 @Parameters(name = "{0}") getParamsnull675 fun getParams(): List<FlagsParameterization> { 676 return FlagsParameterization.allCombinationsOf().andSceneContainer() 677 } 678 } 679 } 680