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