1 /*
2  * Copyright (C) 2021 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.testing.AndroidTestingRunner
20 import android.testing.TestableLooper.RunWithLooper
21 import android.view.KeyEvent
22 import android.view.MotionEvent
23 import android.view.ViewGroup
24 import androidx.test.filters.SmallTest
25 import com.android.keyguard.KeyguardMessageAreaController
26 import com.android.keyguard.KeyguardSecurityContainerController
27 import com.android.keyguard.LockIconViewController
28 import com.android.keyguard.dagger.KeyguardBouncerComponent
29 import com.android.systemui.R
30 import com.android.systemui.SysuiTestCase
31 import com.android.systemui.back.domain.interactor.BackActionInteractor
32 import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
33 import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
34 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
35 import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
36 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
37 import com.android.systemui.classifier.FalsingCollectorFake
38 import com.android.systemui.dock.DockManager
39 import com.android.systemui.dump.logcatLogBuffer
40 import com.android.systemui.flags.FakeFeatureFlags
41 import com.android.systemui.flags.Flags
42 import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
43 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
44 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
45 import com.android.systemui.keyguard.shared.model.TransitionStep
46 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
47 import com.android.systemui.log.BouncerLogger
48 import com.android.systemui.power.domain.interactor.PowerInteractor
49 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
50 import com.android.systemui.statusbar.LockscreenShadeTransitionController
51 import com.android.systemui.statusbar.NotificationInsetsController
52 import com.android.systemui.statusbar.NotificationShadeDepthController
53 import com.android.systemui.statusbar.NotificationShadeWindowController
54 import com.android.systemui.statusbar.SysuiStatusBarStateController
55 import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
56 import com.android.systemui.statusbar.notification.stack.AmbientState
57 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
58 import com.android.systemui.statusbar.phone.CentralSurfaces
59 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
60 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
61 import com.android.systemui.statusbar.window.StatusBarWindowStateController
62 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
63 import com.android.systemui.user.data.repository.FakeUserRepository
64 import com.android.systemui.util.mockito.any
65 import com.android.systemui.util.time.FakeSystemClock
66 import com.google.common.truth.Truth.assertThat
67 import java.util.Optional
68 import kotlinx.coroutines.ExperimentalCoroutinesApi
69 import kotlinx.coroutines.flow.emptyFlow
70 import kotlinx.coroutines.test.TestScope
71 import kotlinx.coroutines.test.runTest
72 import org.junit.Before
73 import org.junit.Test
74 import org.junit.runner.RunWith
75 import org.mockito.ArgumentCaptor
76 import org.mockito.Mock
77 import org.mockito.Mockito.anyFloat
78 import org.mockito.Mockito.mock
79 import org.mockito.Mockito.never
80 import org.mockito.Mockito.verify
81 import org.mockito.Mockito.`when` as whenever
82 import org.mockito.MockitoAnnotations
83 
84 @OptIn(ExperimentalCoroutinesApi::class)
85 @SmallTest
86 @RunWith(AndroidTestingRunner::class)
87 @RunWithLooper(setAsMainLooper = true)
88 class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
89 
90     @Mock private lateinit var view: NotificationShadeWindowView
91     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
92     @Mock private lateinit var centralSurfaces: CentralSurfaces
93     @Mock private lateinit var backActionInteractor: BackActionInteractor
94     @Mock private lateinit var powerInteractor: PowerInteractor
95     @Mock private lateinit var dockManager: DockManager
96     @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
97     @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
98     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
99     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
100     @Mock private lateinit var shadeLogger: ShadeLogger
101     @Mock private lateinit var ambientState: AmbientState
102     @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
103     @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
104     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
105     @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
106     @Mock
107     private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
108     @Mock private lateinit var lockIconViewController: LockIconViewController
109     @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
110     @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
111     @Mock
112     private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
113     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
114     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
115     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
116     @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
117     @Mock
118     private lateinit var unfoldTransitionProgressProvider:
119         Optional<UnfoldTransitionProgressProvider>
120     @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
121     @Mock
122     lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
123     @Mock lateinit var keyEventInteractor: KeyEventInteractor
124     private val notificationExpansionRepository = NotificationExpansionRepository()
125 
126     private lateinit var fakeClock: FakeSystemClock
127     private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
128     private lateinit var interactionEventHandler: InteractionEventHandler
129 
130     private lateinit var underTest: NotificationShadeWindowViewController
131 
132     private lateinit var testScope: TestScope
133 
134     @Before
135     fun setUp() {
136         MockitoAnnotations.initMocks(this)
137         whenever(view.bottom).thenReturn(VIEW_BOTTOM)
138         whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container))
139             .thenReturn(mock(ViewGroup::class.java))
140         whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java)))
141             .thenReturn(keyguardBouncerComponent)
142         whenever(keyguardBouncerComponent.securityContainerController)
143             .thenReturn(keyguardSecurityContainerController)
144         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
145             .thenReturn(emptyFlow<TransitionStep>())
146 
147         val featureFlags = FakeFeatureFlags()
148         featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
149         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
150         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
151         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
152         featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
153 
154         testScope = TestScope()
155         fakeClock = FakeSystemClock()
156         underTest =
157             NotificationShadeWindowViewController(
158                     lockscreenShadeTransitionController,
159                     FalsingCollectorFake(),
160                     sysuiStatusBarStateController,
161                     dockManager,
162                     notificationShadeDepthController,
163                     view,
164                     notificationPanelViewController,
165                     ShadeExpansionStateManager(),
166                     stackScrollLayoutController,
167                     statusBarKeyguardViewManager,
168                     statusBarWindowStateController,
169                     lockIconViewController,
170                     centralSurfaces,
171                     backActionInteractor,
172                     powerInteractor,
173                     notificationShadeWindowController,
174                     unfoldTransitionProgressProvider,
175                     keyguardUnlockAnimationController,
176                     notificationInsetsController,
177                     ambientState,
178                     shadeLogger,
179                     pulsingGestureListener,
180                     mLockscreenHostedDreamGestureListener,
181                     keyguardBouncerViewModel,
182                     keyguardBouncerComponentFactory,
183                     mock(KeyguardMessageAreaController.Factory::class.java),
184                     keyguardTransitionInteractor,
185                     primaryBouncerToGoneTransitionViewModel,
186                     notificationExpansionRepository,
187                     featureFlags,
188                     fakeClock,
189                     BouncerMessageInteractor(
190                         FakeBouncerMessageRepository(),
191                         mock(BouncerMessageFactory::class.java),
192                         FakeUserRepository(),
193                         CountDownTimerUtil(),
194                         featureFlags
195                     ),
196                     BouncerLogger(logcatLogBuffer("BouncerLog")),
197                     keyEventInteractor,
198             )
199         underTest.setupExpandedStatusBar()
200 
201         interactionEventHandlerCaptor = ArgumentCaptor.forClass(InteractionEventHandler::class.java)
202         verify(view).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
203         interactionEventHandler = interactionEventHandlerCaptor.value
204     }
205 
206     // Note: So far, these tests only cover interactions with the status bar view controller. More
207     // tests need to be added to test the rest of handleDispatchTouchEvent.
208 
209     @Test
210     fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() =
211         testScope.runTest {
212             underTest.setStatusBarViewController(null)
213 
214             val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
215 
216             assertThat(returnVal).isFalse()
217         }
218 
219     @Test
220     fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() =
221         testScope.runTest {
222             underTest.setStatusBarViewController(phoneStatusBarViewController)
223             val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
224             whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
225 
226             val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev)
227 
228             verify(phoneStatusBarViewController).sendTouchToView(ev)
229             assertThat(returnVal).isTrue()
230         }
231 
232     @Test
233     fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() =
234         testScope.runTest {
235             underTest.setStatusBarViewController(phoneStatusBarViewController)
236             val downEvBelow =
237                 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
238             interactionEventHandler.handleDispatchTouchEvent(downEvBelow)
239 
240             val nextEvent =
241                 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
242             whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
243 
244             val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
245 
246             verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
247             assertThat(returnVal).isTrue()
248         }
249 
250     @Test
251     fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() =
252         testScope.runTest {
253             underTest.setStatusBarViewController(phoneStatusBarViewController)
254             whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
255             whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
256             whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
257                 .thenReturn(true)
258             whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true)
259 
260             val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
261 
262             verify(phoneStatusBarViewController).sendTouchToView(DOWN_EVENT)
263             assertThat(returnVal).isTrue()
264         }
265 
266     @Test
267     fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() =
268         testScope.runTest {
269             underTest.setStatusBarViewController(phoneStatusBarViewController)
270             whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
271             whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
272                 .thenReturn(true)
273             // Item we're testing
274             whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)
275 
276             val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
277 
278             verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
279             assertThat(returnVal).isNull()
280         }
281 
282     @Test
283     fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() =
284         testScope.runTest {
285             underTest.setStatusBarViewController(phoneStatusBarViewController)
286             whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
287             whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
288             // Item we're testing
289             whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
290                 .thenReturn(false)
291 
292             val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
293 
294             verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
295             assertThat(returnVal).isNull()
296         }
297 
298     @Test
299     fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() =
300         testScope.runTest {
301             underTest.setStatusBarViewController(phoneStatusBarViewController)
302             whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
303             whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
304                 .thenReturn(true)
305             // Item we're testing
306             whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false)
307 
308             val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
309 
310             verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
311             assertThat(returnVal).isTrue()
312         }
313 
314     @Test
315     fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() =
316         testScope.runTest {
317             underTest.setStatusBarViewController(phoneStatusBarViewController)
318             whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
319             whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
320             whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
321                 .thenReturn(true)
322 
323             // Down event first
324             interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
325 
326             // Then another event
327             val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
328             whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
329 
330             val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
331 
332             verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
333             assertThat(returnVal).isTrue()
334         }
335 
336     @Test
337     fun handleDispatchTouchEvent_launchAnimationRunningTimesOut() =
338         testScope.runTest {
339             // GIVEN touch dispatcher in a state that returns true
340             underTest.setStatusBarViewController(phoneStatusBarViewController)
341             whenever(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(
342                 true
343             )
344             assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
345 
346             // WHEN launch animation is running for 2 seconds
347             fakeClock.setUptimeMillis(10000)
348             underTest.setExpandAnimationRunning(true)
349             fakeClock.advanceTime(2000)
350 
351             // THEN touch is ignored
352             assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isFalse()
353 
354             // WHEN Launch animation is running for 6 seconds
355             fakeClock.advanceTime(4000)
356 
357             // THEN move is ignored, down is handled, and window is notified
358             assertThat(interactionEventHandler.handleDispatchTouchEvent(MOVE_EVENT)).isFalse()
359             assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
360             verify(notificationShadeWindowController).setLaunchingActivity(false)
361         }
362 
363     @Test
364     fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() {
365         // down event should be intercepted by keyguardViewManager
366         whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
367             .thenReturn(true)
368 
369         // Then touch should not be intercepted
370         val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
371         assertThat(shouldIntercept).isTrue()
372     }
373 
374     @Test
375     fun testGetKeyguardMessageArea() =
376         testScope.runTest {
377             underTest.keyguardMessageArea
378             verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
379         }
380 
381     @Test
382     fun forwardsDispatchKeyEvent() {
383         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
384         interactionEventHandler.dispatchKeyEvent(keyEvent)
385         verify(keyEventInteractor).dispatchKeyEvent(keyEvent)
386     }
387 
388     @Test
389     fun forwardsDispatchKeyEventPreIme() {
390         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
391         interactionEventHandler.dispatchKeyEventPreIme(keyEvent)
392         verify(keyEventInteractor).dispatchKeyEventPreIme(keyEvent)
393     }
394 
395     @Test
396     fun forwardsInterceptMediaKey() {
397         val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
398         interactionEventHandler.interceptMediaKey(keyEvent)
399         verify(keyEventInteractor).interceptMediaKey(keyEvent)
400     }
401 
402     companion object {
403         private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
404         private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
405         private const val VIEW_BOTTOM = 100
406     }
407 }
408