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