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