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.MotionEvent 22 import android.view.ViewGroup 23 import androidx.test.filters.SmallTest 24 import com.android.keyguard.KeyguardSecurityContainerController 25 import com.android.keyguard.LockIconViewController 26 import com.android.keyguard.dagger.KeyguardBouncerComponent 27 import com.android.systemui.R 28 import com.android.systemui.SysuiTestCase 29 import com.android.systemui.classifier.FalsingCollectorFake 30 import com.android.systemui.dock.DockManager 31 import com.android.systemui.keyguard.KeyguardUnlockAnimationController 32 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor 33 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 34 import com.android.systemui.keyguard.shared.model.TransitionStep 35 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel 36 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel 37 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler 38 import com.android.systemui.statusbar.LockscreenShadeTransitionController 39 import com.android.systemui.statusbar.NotificationInsetsController 40 import com.android.systemui.statusbar.NotificationShadeDepthController 41 import com.android.systemui.statusbar.NotificationShadeWindowController 42 import com.android.systemui.statusbar.SysuiStatusBarStateController 43 import com.android.systemui.statusbar.notification.stack.AmbientState 44 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController 45 import com.android.systemui.statusbar.phone.CentralSurfaces 46 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController 47 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager 48 import com.android.systemui.statusbar.window.StatusBarWindowStateController 49 import com.android.systemui.util.mockito.any 50 import com.google.common.truth.Truth.assertThat 51 import kotlinx.coroutines.flow.emptyFlow 52 import org.junit.Before 53 import org.junit.Test 54 import org.junit.runner.RunWith 55 import org.mockito.ArgumentCaptor 56 import org.mockito.Mock 57 import org.mockito.Mockito 58 import org.mockito.Mockito.anyFloat 59 import org.mockito.Mockito.mock 60 import org.mockito.Mockito.never 61 import org.mockito.Mockito.verify 62 import org.mockito.Mockito.`when` as whenever 63 import org.mockito.MockitoAnnotations 64 65 @SmallTest 66 @RunWith(AndroidTestingRunner::class) 67 @RunWithLooper(setAsMainLooper = true) 68 class NotificationShadeWindowViewControllerTest : SysuiTestCase() { 69 @Mock private lateinit var view: NotificationShadeWindowView 70 @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController 71 @Mock private lateinit var centralSurfaces: CentralSurfaces 72 @Mock private lateinit var dockManager: DockManager 73 @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController 74 @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController 75 @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController 76 @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController 77 @Mock private lateinit var ambientState: AmbientState 78 @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel 79 @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController 80 @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager 81 @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController 82 @Mock 83 private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController 84 @Mock private lateinit var lockIconViewController: LockIconViewController 85 @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController 86 @Mock private lateinit var pulsingGestureListener: PulsingGestureListener 87 @Mock private lateinit var notificationInsetsController: NotificationInsetsController 88 @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor 89 @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory 90 @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent 91 @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController 92 @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor 93 @Mock 94 lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel 95 96 private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler> 97 private lateinit var interactionEventHandler: InteractionEventHandler 98 99 private lateinit var underTest: NotificationShadeWindowViewController 100 101 @Before setUpnull102 fun setUp() { 103 MockitoAnnotations.initMocks(this) 104 whenever(view.bottom).thenReturn(VIEW_BOTTOM) 105 whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container)) 106 .thenReturn(mock(ViewGroup::class.java)) 107 whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java))) 108 .thenReturn(keyguardBouncerComponent) 109 whenever(keyguardBouncerComponent.securityContainerController) 110 .thenReturn(keyguardSecurityContainerController) 111 whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) 112 .thenReturn(emptyFlow<TransitionStep>()) 113 underTest = 114 NotificationShadeWindowViewController( 115 lockscreenShadeTransitionController, 116 FalsingCollectorFake(), 117 sysuiStatusBarStateController, 118 dockManager, 119 notificationShadeDepthController, 120 view, 121 notificationPanelViewController, 122 ShadeExpansionStateManager(), 123 stackScrollLayoutController, 124 statusBarKeyguardViewManager, 125 statusBarWindowStateController, 126 lockIconViewController, 127 centralSurfaces, 128 notificationShadeWindowController, 129 keyguardUnlockAnimationController, 130 notificationInsetsController, 131 ambientState, 132 pulsingGestureListener, 133 keyguardBouncerViewModel, 134 keyguardBouncerComponentFactory, 135 alternateBouncerInteractor, 136 keyguardTransitionInteractor, 137 primaryBouncerToGoneTransitionViewModel, 138 ) 139 underTest.setupExpandedStatusBar() 140 141 interactionEventHandlerCaptor = ArgumentCaptor.forClass(InteractionEventHandler::class.java) 142 verify(view).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) 143 interactionEventHandler = interactionEventHandlerCaptor.value 144 } 145 146 // Note: So far, these tests only cover interactions with the status bar view controller. More 147 // tests need to be added to test the rest of handleDispatchTouchEvent. 148 149 @Test handleDispatchTouchEvent_nullStatusBarViewController_returnsFalsenull150 fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() { 151 underTest.setStatusBarViewController(null) 152 153 val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) 154 155 assertThat(returnVal).isFalse() 156 } 157 158 @Test handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSbnull159 fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() { 160 underTest.setStatusBarViewController(phoneStatusBarViewController) 161 val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) 162 whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true) 163 164 val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev) 165 166 verify(phoneStatusBarViewController).sendTouchToView(ev) 167 assertThat(returnVal).isTrue() 168 } 169 170 @Test handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSbnull171 fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() { 172 underTest.setStatusBarViewController(phoneStatusBarViewController) 173 val downEvBelow = 174 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) 175 interactionEventHandler.handleDispatchTouchEvent(downEvBelow) 176 177 val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0) 178 whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) 179 180 val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) 181 182 verify(phoneStatusBarViewController).sendTouchToView(nextEvent) 183 assertThat(returnVal).isTrue() 184 } 185 186 @Test handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSbnull187 fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() { 188 underTest.setStatusBarViewController(phoneStatusBarViewController) 189 whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) 190 whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) 191 whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) 192 .thenReturn(true) 193 whenever(phoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true) 194 195 val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) 196 197 verify(phoneStatusBarViewController).sendTouchToView(downEv) 198 assertThat(returnVal).isTrue() 199 } 200 201 @Test handleDispatchTouchEvent_panelNotCollapsed_returnsNullnull202 fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() { 203 underTest.setStatusBarViewController(phoneStatusBarViewController) 204 whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) 205 whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) 206 .thenReturn(true) 207 // Item we're testing 208 whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false) 209 210 val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) 211 212 verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) 213 assertThat(returnVal).isNull() 214 } 215 216 @Test handleDispatchTouchEvent_touchNotInSbBounds_returnsNullnull217 fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() { 218 underTest.setStatusBarViewController(phoneStatusBarViewController) 219 whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) 220 whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) 221 // Item we're testing 222 whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) 223 .thenReturn(false) 224 225 val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) 226 227 verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) 228 assertThat(returnVal).isNull() 229 } 230 231 @Test handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTruenull232 fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() { 233 underTest.setStatusBarViewController(phoneStatusBarViewController) 234 whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) 235 whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) 236 .thenReturn(true) 237 // Item we're testing 238 whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false) 239 240 val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) 241 242 verify(phoneStatusBarViewController, never()).sendTouchToView(downEv) 243 assertThat(returnVal).isTrue() 244 } 245 246 @Test handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSbnull247 fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() { 248 underTest.setStatusBarViewController(phoneStatusBarViewController) 249 whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) 250 whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true) 251 whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) 252 .thenReturn(true) 253 254 // Down event first 255 interactionEventHandler.handleDispatchTouchEvent(downEv) 256 257 // Then another event 258 val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) 259 whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) 260 261 val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent) 262 263 verify(phoneStatusBarViewController).sendTouchToView(nextEvent) 264 assertThat(returnVal).isTrue() 265 } 266 267 @Test testGetBouncerContainernull268 fun testGetBouncerContainer() { 269 Mockito.clearInvocations(view) 270 underTest.bouncerContainer 271 verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container) 272 } 273 274 @Test testGetKeyguardMessageAreanull275 fun testGetKeyguardMessageArea() { 276 underTest.keyguardMessageArea 277 verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) 278 } 279 280 @Test handleDispatchTouchEvent_statusBarViewControllerOnTouch_returnsTruenull281 fun handleDispatchTouchEvent_statusBarViewControllerOnTouch_returnsTrue() { 282 underTest.setStatusBarViewController(phoneStatusBarViewController) 283 284 // GIVEN the statusBarKeyguardViewManager will handle any touches 285 whenever(statusBarKeyguardViewManager.onTouch(any())).thenReturn(true) 286 287 // WHEN a touch is dispatched 288 val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv) 289 290 // THEN handleDispatchTouchEvent returns true 291 assertThat(returnVal).isTrue() 292 } 293 } 294 295 private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) 296 private const val VIEW_BOTTOM = 100 297