1 /* 2 * Copyright (C) 2020 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.biometrics; 18 19 import static org.junit.Assert.assertFalse; 20 import static org.junit.Assert.assertTrue; 21 import static org.mockito.ArgumentMatchers.anyBoolean; 22 import static org.mockito.ArgumentMatchers.anyInt; 23 import static org.mockito.Mockito.atLeast; 24 import static org.mockito.Mockito.eq; 25 import static org.mockito.Mockito.never; 26 import static org.mockito.Mockito.reset; 27 import static org.mockito.Mockito.times; 28 import static org.mockito.Mockito.verify; 29 import static org.mockito.Mockito.when; 30 31 import android.content.Context; 32 import android.testing.AndroidTestingRunner; 33 import android.testing.TestableLooper.RunWithLooper; 34 35 import androidx.test.filters.SmallTest; 36 37 import com.android.keyguard.KeyguardUpdateMonitor; 38 import com.android.systemui.SysuiTestCase; 39 import com.android.systemui.dump.DumpManager; 40 import com.android.systemui.keyguard.KeyguardViewMediator; 41 import com.android.systemui.plugins.statusbar.StatusBarStateController; 42 import com.android.systemui.statusbar.LockscreenShadeTransitionController; 43 import com.android.systemui.statusbar.StatusBarState; 44 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 45 import com.android.systemui.statusbar.phone.SystemUIDialogManager; 46 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; 47 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener; 48 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; 49 import com.android.systemui.statusbar.policy.ConfigurationController; 50 import com.android.systemui.statusbar.policy.KeyguardStateController; 51 import com.android.systemui.util.concurrency.DelayableExecutor; 52 import com.android.systemui.util.time.FakeSystemClock; 53 54 import org.junit.Before; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 import org.mockito.ArgumentCaptor; 58 import org.mockito.Captor; 59 import org.mockito.Mock; 60 import org.mockito.MockitoAnnotations; 61 62 import java.util.List; 63 64 @SmallTest 65 @RunWith(AndroidTestingRunner.class) 66 @RunWithLooper 67 public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { 68 // Dependencies 69 @Mock 70 private UdfpsKeyguardView mView; 71 @Mock 72 private Context mResourceContext; 73 @Mock 74 private StatusBarStateController mStatusBarStateController; 75 @Mock 76 private PanelExpansionStateManager mPanelExpansionStateManager; 77 @Mock 78 private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 79 @Mock 80 private LockscreenShadeTransitionController mLockscreenShadeTransitionController; 81 @Mock 82 private DumpManager mDumpManager; 83 @Mock 84 private DelayableExecutor mExecutor; 85 @Mock 86 private KeyguardUpdateMonitor mKeyguardUpdateMonitor; 87 @Mock 88 private KeyguardStateController mKeyguardStateController; 89 @Mock 90 private KeyguardViewMediator mKeyguardViewMediator; 91 @Mock 92 private ConfigurationController mConfigurationController; 93 @Mock 94 private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; 95 @Mock 96 private SystemUIDialogManager mDialogManager; 97 @Mock 98 private UdfpsController mUdfpsController; 99 private FakeSystemClock mSystemClock = new FakeSystemClock(); 100 101 private UdfpsKeyguardViewController mController; 102 103 // Capture listeners so that they can be used to send events 104 @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor; 105 private StatusBarStateController.StateListener mStatusBarStateListener; 106 107 @Captor private ArgumentCaptor<PanelExpansionListener> mExpansionListenerCaptor; 108 private List<PanelExpansionListener> mExpansionListeners; 109 110 @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor> 111 mAltAuthInterceptorCaptor; 112 private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor; 113 114 @Captor private ArgumentCaptor<KeyguardStateController.Callback> 115 mKeyguardStateControllerCallbackCaptor; 116 private KeyguardStateController.Callback mKeyguardStateControllerCallback; 117 118 @Before setUp()119 public void setUp() { 120 MockitoAnnotations.initMocks(this); 121 when(mView.getContext()).thenReturn(mResourceContext); 122 when(mResourceContext.getString(anyInt())).thenReturn("test string"); 123 when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false); 124 mController = new UdfpsKeyguardViewController( 125 mView, 126 mStatusBarStateController, 127 mPanelExpansionStateManager, 128 mStatusBarKeyguardViewManager, 129 mKeyguardUpdateMonitor, 130 mDumpManager, 131 mLockscreenShadeTransitionController, 132 mConfigurationController, 133 mSystemClock, 134 mKeyguardStateController, 135 mUnlockedScreenOffAnimationController, 136 mDialogManager, 137 mUdfpsController); 138 } 139 140 @Test testRegistersExpansionChangedListenerOnAttached()141 public void testRegistersExpansionChangedListenerOnAttached() { 142 mController.onViewAttached(); 143 captureExpansionListeners(); 144 } 145 146 @Test testRegistersStatusBarStateListenersOnAttached()147 public void testRegistersStatusBarStateListenersOnAttached() { 148 mController.onViewAttached(); 149 captureStatusBarStateListeners(); 150 } 151 152 @Test testViewControllerQueriesSBStateOnAttached()153 public void testViewControllerQueriesSBStateOnAttached() { 154 mController.onViewAttached(); 155 verify(mStatusBarStateController).getState(); 156 verify(mStatusBarStateController).getDozeAmount(); 157 158 final float dozeAmount = .88f; 159 when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); 160 when(mStatusBarStateController.getDozeAmount()).thenReturn(dozeAmount); 161 captureStatusBarStateListeners(); 162 163 mController.onViewAttached(); 164 verify(mView, atLeast(1)).setPauseAuth(true); 165 verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount); 166 } 167 168 @Test testListenersUnregisteredOnDetached()169 public void testListenersUnregisteredOnDetached() { 170 mController.onViewAttached(); 171 captureStatusBarStateListeners(); 172 captureExpansionListeners(); 173 captureKeyguardStateControllerCallback(); 174 mController.onViewDetached(); 175 176 verify(mStatusBarStateController).removeCallback(mStatusBarStateListener); 177 for (PanelExpansionListener listener : mExpansionListeners) { 178 verify(mPanelExpansionStateManager).removeExpansionListener(listener); 179 } 180 verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback); 181 } 182 183 @Test testDozeEventsSentToView()184 public void testDozeEventsSentToView() { 185 mController.onViewAttached(); 186 captureStatusBarStateListeners(); 187 188 final float linear = .55f; 189 final float eased = .65f; 190 mStatusBarStateListener.onDozeAmountChanged(linear, eased); 191 192 verify(mView).onDozeAmountChanged(linear, eased); 193 } 194 195 @Test testShouldPauseAuthBouncerShowing()196 public void testShouldPauseAuthBouncerShowing() { 197 mController.onViewAttached(); 198 captureStatusBarStateListeners(); 199 200 sendStatusBarStateChanged(StatusBarState.KEYGUARD); 201 202 assertFalse(mController.shouldPauseAuth()); 203 } 204 205 @Test testShouldNotPauseAuthOnKeyguard()206 public void testShouldNotPauseAuthOnKeyguard() { 207 mController.onViewAttached(); 208 captureStatusBarStateListeners(); 209 210 sendStatusBarStateChanged(StatusBarState.KEYGUARD); 211 212 assertFalse(mController.shouldPauseAuth()); 213 } 214 215 @Test testShouldPauseAuthIsLaunchTransitionFadingAway()216 public void testShouldPauseAuthIsLaunchTransitionFadingAway() { 217 // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard) 218 mController.onViewAttached(); 219 captureStatusBarStateListeners(); 220 sendStatusBarStateChanged(StatusBarState.KEYGUARD); 221 222 // WHEN isLaunchTransitionFadingAway=true 223 captureKeyguardStateControllerCallback(); 224 when(mKeyguardStateController.isLaunchTransitionFadingAway()).thenReturn(true); 225 mKeyguardStateControllerCallback.onLaunchTransitionFadingAwayChanged(); 226 227 // THEN pause auth 228 assertTrue(mController.shouldPauseAuth()); 229 } 230 231 @Test testShouldPauseAuthOnShadeLocked()232 public void testShouldPauseAuthOnShadeLocked() { 233 mController.onViewAttached(); 234 captureStatusBarStateListeners(); 235 236 sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED); 237 238 assertTrue(mController.shouldPauseAuth()); 239 } 240 241 @Test testShouldPauseAuthOnShade()242 public void testShouldPauseAuthOnShade() { 243 mController.onViewAttached(); 244 captureStatusBarStateListeners(); 245 246 // WHEN not on keyguard yet (shade = home) 247 sendStatusBarStateChanged(StatusBarState.SHADE); 248 249 // THEN pause auth 250 assertTrue(mController.shouldPauseAuth()); 251 } 252 253 @Test testShouldPauseAuthAnimatingScreenOffFromShade()254 public void testShouldPauseAuthAnimatingScreenOffFromShade() { 255 mController.onViewAttached(); 256 captureStatusBarStateListeners(); 257 258 // WHEN transitioning from home/shade => keyguard + animating screen off 259 mStatusBarStateListener.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD); 260 when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true); 261 262 // THEN pause auth 263 assertTrue(mController.shouldPauseAuth()); 264 } 265 266 @Test testDoNotPauseAuthAnimatingScreenOffFromLS()267 public void testDoNotPauseAuthAnimatingScreenOffFromLS() { 268 mController.onViewAttached(); 269 captureStatusBarStateListeners(); 270 271 // WHEN animating screen off transition from LS => AOD 272 sendStatusBarStateChanged(StatusBarState.KEYGUARD); 273 when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true); 274 275 // THEN don't pause auth 276 assertFalse(mController.shouldPauseAuth()); 277 } 278 279 @Test testOverrideShouldPauseAuthOnShadeLocked()280 public void testOverrideShouldPauseAuthOnShadeLocked() { 281 mController.onViewAttached(); 282 captureStatusBarStateListeners(); 283 captureAltAuthInterceptor(); 284 285 sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED); 286 assertTrue(mController.shouldPauseAuth()); 287 288 mAltAuthInterceptor.showAlternateAuthBouncer(); // force show 289 assertFalse(mController.shouldPauseAuth()); 290 assertTrue(mAltAuthInterceptor.isShowingAlternateAuthBouncer()); 291 292 mAltAuthInterceptor.hideAlternateAuthBouncer(); // stop force show 293 assertTrue(mController.shouldPauseAuth()); 294 assertFalse(mAltAuthInterceptor.isShowingAlternateAuthBouncer()); 295 } 296 297 @Test testOnDetachedStateReset()298 public void testOnDetachedStateReset() { 299 // GIVEN view is attached 300 mController.onViewAttached(); 301 captureAltAuthInterceptor(); 302 303 // WHEN view is detached 304 mController.onViewDetached(); 305 306 // THEN remove alternate auth interceptor 307 verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor); 308 } 309 310 @Test testHiddenUdfpsBouncerOnTouchOutside_nothingHappens()311 public void testHiddenUdfpsBouncerOnTouchOutside_nothingHappens() { 312 // GIVEN view is attached 313 mController.onViewAttached(); 314 captureAltAuthInterceptor(); 315 316 // GIVEN udfps bouncer isn't showing 317 mAltAuthInterceptor.hideAlternateAuthBouncer(); 318 319 // WHEN touch is observed outside the view 320 mController.onTouchOutsideView(); 321 322 // THEN bouncer / alt auth methods are never called 323 verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean()); 324 verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean()); 325 } 326 327 @Test testShowingUdfpsBouncerOnTouchOutsideWithinThreshold_nothingHappens()328 public void testShowingUdfpsBouncerOnTouchOutsideWithinThreshold_nothingHappens() { 329 // GIVEN view is attached 330 mController.onViewAttached(); 331 captureAltAuthInterceptor(); 332 333 // GIVEN udfps bouncer is showing 334 mAltAuthInterceptor.showAlternateAuthBouncer(); 335 336 // WHEN touch is observed outside the view 200ms later (just within threshold) 337 mSystemClock.advanceTime(200); 338 mController.onTouchOutsideView(); 339 340 // THEN bouncer / alt auth methods are never called because not enough time has passed 341 verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean()); 342 verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean()); 343 } 344 345 @Test testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer()346 public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer() { 347 // GIVEN view is attached 348 mController.onViewAttached(); 349 captureAltAuthInterceptor(); 350 351 // GIVEN udfps bouncer is showing 352 mAltAuthInterceptor.showAlternateAuthBouncer(); 353 354 // WHEN touch is observed outside the view 205ms later 355 mSystemClock.advanceTime(205); 356 mController.onTouchOutsideView(); 357 358 // THEN show the bouncer 359 verify(mStatusBarKeyguardViewManager).showBouncer(eq(true)); 360 } 361 362 @Test testFadeInWithStatusBarExpansion()363 public void testFadeInWithStatusBarExpansion() { 364 // GIVEN view is attached 365 mController.onViewAttached(); 366 captureExpansionListeners(); 367 captureKeyguardStateControllerCallback(); 368 reset(mView); 369 370 // WHEN status bar expansion is 0 371 updateStatusBarExpansion(0, true); 372 373 // THEN alpha is 0 374 verify(mView).setUnpausedAlpha(0); 375 } 376 377 @Test testShowUdfpsBouncer()378 public void testShowUdfpsBouncer() { 379 // GIVEN view is attached and status bar expansion is 0 380 mController.onViewAttached(); 381 captureExpansionListeners(); 382 captureKeyguardStateControllerCallback(); 383 captureAltAuthInterceptor(); 384 updateStatusBarExpansion(0, true); 385 reset(mView); 386 when(mView.getContext()).thenReturn(mResourceContext); 387 when(mResourceContext.getString(anyInt())).thenReturn("test string"); 388 389 // WHEN status bar expansion is 0 but udfps bouncer is requested 390 mAltAuthInterceptor.showAlternateAuthBouncer(); 391 392 // THEN alpha is 255 393 verify(mView).setUnpausedAlpha(255); 394 } 395 396 @Test testTransitionToFullShadeProgress()397 public void testTransitionToFullShadeProgress() { 398 // GIVEN view is attached and status bar expansion is 1f 399 mController.onViewAttached(); 400 captureExpansionListeners(); 401 updateStatusBarExpansion(1f, true); 402 reset(mView); 403 404 // WHEN we're transitioning to the full shade 405 float transitionProgress = .6f; 406 mController.setTransitionToFullShadeProgress(transitionProgress); 407 408 // THEN alpha is between 0 and 255 409 verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255)); 410 } 411 412 @Test testShowUdfpsBouncer_transitionToFullShadeProgress()413 public void testShowUdfpsBouncer_transitionToFullShadeProgress() { 414 // GIVEN view is attached and status bar expansion is 1f 415 mController.onViewAttached(); 416 captureExpansionListeners(); 417 captureKeyguardStateControllerCallback(); 418 captureAltAuthInterceptor(); 419 updateStatusBarExpansion(1f, true); 420 mAltAuthInterceptor.showAlternateAuthBouncer(); 421 reset(mView); 422 423 // WHEN we're transitioning to the full shade 424 mController.setTransitionToFullShadeProgress(1.0f); 425 426 // THEN alpha is 255 (b/c udfps bouncer is requested) 427 verify(mView).setUnpausedAlpha(255); 428 } 429 sendStatusBarStateChanged(int statusBarState)430 private void sendStatusBarStateChanged(int statusBarState) { 431 mStatusBarStateListener.onStateChanged(statusBarState); 432 } 433 captureStatusBarStateListeners()434 private void captureStatusBarStateListeners() { 435 verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture()); 436 mStatusBarStateListener = mStateListenerCaptor.getValue(); 437 } 438 captureExpansionListeners()439 private void captureExpansionListeners() { 440 verify(mPanelExpansionStateManager, times(2)) 441 .addExpansionListener(mExpansionListenerCaptor.capture()); 442 // first (index=0) is from super class, UdfpsAnimationViewController. 443 // second (index=1) is from UdfpsKeyguardViewController 444 mExpansionListeners = mExpansionListenerCaptor.getAllValues(); 445 } 446 updateStatusBarExpansion(float fraction, boolean expanded)447 private void updateStatusBarExpansion(float fraction, boolean expanded) { 448 for (PanelExpansionListener listener : mExpansionListeners) { 449 listener.onPanelExpansionChanged(fraction, expanded, /* tracking= */ false); 450 } 451 } 452 captureAltAuthInterceptor()453 private void captureAltAuthInterceptor() { 454 verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor( 455 mAltAuthInterceptorCaptor.capture()); 456 mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue(); 457 } 458 459 460 captureKeyguardStateControllerCallback()461 private void captureKeyguardStateControllerCallback() { 462 verify(mKeyguardStateController).addCallback( 463 mKeyguardStateControllerCallbackCaptor.capture()); 464 mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue(); 465 } 466 } 467