blob: 4b35de15147b4b058f0c28d22a03ab2a817235bc [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
// Dependencies
@Mock
private UdfpsKeyguardView mView;
@Mock
private Context mResourceContext;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
private StatusBar mStatusBar;
@Mock
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock
private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Mock
private DumpManager mDumpManager;
@Mock
private DelayableExecutor mExecutor;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
private KeyguardStateController mKeyguardStateController;
@Mock
private KeyguardViewMediator mKeyguardViewMediator;
@Mock
private ConfigurationController mConfigurationController;
@Mock
private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@Mock
private UdfpsController mUdfpsController;
private FakeSystemClock mSystemClock = new FakeSystemClock();
private UdfpsKeyguardViewController mController;
// Capture listeners so that they can be used to send events
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
private StatusBarStateController.StateListener mStatusBarStateListener;
@Captor private ArgumentCaptor<StatusBar.ExpansionChangedListener> mExpansionListenerCaptor;
private List<StatusBar.ExpansionChangedListener> mExpansionListeners;
@Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
mAltAuthInterceptorCaptor;
private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
@Captor private ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallbackCaptor;
private KeyguardStateController.Callback mKeyguardStateControllerCallback;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mView.getContext()).thenReturn(mResourceContext);
when(mResourceContext.getString(anyInt())).thenReturn("test string");
when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
mController = new UdfpsKeyguardViewController(
mView,
mStatusBarStateController,
mStatusBar,
mStatusBarKeyguardViewManager,
mKeyguardUpdateMonitor,
mDumpManager,
mLockscreenShadeTransitionController,
mConfigurationController,
mSystemClock,
mKeyguardStateController,
mUnlockedScreenOffAnimationController,
mUdfpsController);
}
@Test
public void testRegistersExpansionChangedListenerOnAttached() {
mController.onViewAttached();
captureExpansionListeners();
}
@Test
public void testRegistersStatusBarStateListenersOnAttached() {
mController.onViewAttached();
captureStatusBarStateListeners();
}
@Test
public void testViewControllerQueriesSBStateOnAttached() {
mController.onViewAttached();
verify(mStatusBarStateController).getState();
verify(mStatusBarStateController).getDozeAmount();
final float dozeAmount = .88f;
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
when(mStatusBarStateController.getDozeAmount()).thenReturn(dozeAmount);
captureStatusBarStateListeners();
mController.onViewAttached();
verify(mView, atLeast(1)).setPauseAuth(true);
verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount);
}
@Test
public void testListenersUnregisteredOnDetached() {
mController.onViewAttached();
captureStatusBarStateListeners();
captureExpansionListeners();
captureKeyguardStateControllerCallback();
mController.onViewDetached();
verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) {
verify(mStatusBar).removeExpansionChangedListener(listener);
}
verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback);
}
@Test
public void testDozeEventsSentToView() {
mController.onViewAttached();
captureStatusBarStateListeners();
final float linear = .55f;
final float eased = .65f;
mStatusBarStateListener.onDozeAmountChanged(linear, eased);
verify(mView).onDozeAmountChanged(linear, eased);
}
@Test
public void testShouldPauseAuthBouncerShowing() {
mController.onViewAttached();
captureStatusBarStateListeners();
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
assertFalse(mController.shouldPauseAuth());
}
@Test
public void testShouldNotPauseAuthOnKeyguard() {
mController.onViewAttached();
captureStatusBarStateListeners();
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
assertFalse(mController.shouldPauseAuth());
}
@Test
public void testShouldPauseAuthIsLaunchTransitionFadingAway() {
// GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard)
mController.onViewAttached();
captureStatusBarStateListeners();
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
// WHEN isLaunchTransitionFadingAway=true
captureKeyguardStateControllerCallback();
when(mKeyguardStateController.isLaunchTransitionFadingAway()).thenReturn(true);
mKeyguardStateControllerCallback.onLaunchTransitionFadingAwayChanged();
// THEN pause auth
assertTrue(mController.shouldPauseAuth());
}
@Test
public void testShouldPauseAuthOnShadeLocked() {
mController.onViewAttached();
captureStatusBarStateListeners();
sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
assertTrue(mController.shouldPauseAuth());
}
@Test
public void testShouldPauseAuthOnShade() {
mController.onViewAttached();
captureStatusBarStateListeners();
// WHEN not on keyguard yet (shade = home)
sendStatusBarStateChanged(StatusBarState.SHADE);
// THEN pause auth
assertTrue(mController.shouldPauseAuth());
}
@Test
public void testShouldPauseAuthAnimatingScreenOffFromShade() {
mController.onViewAttached();
captureStatusBarStateListeners();
// WHEN transitioning from home/shade => keyguard + animating screen off
mStatusBarStateListener.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD);
when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true);
// THEN pause auth
assertTrue(mController.shouldPauseAuth());
}
@Test
public void testDoNotPauseAuthAnimatingScreenOffFromLS() {
mController.onViewAttached();
captureStatusBarStateListeners();
// WHEN animating screen off transition from LS => AOD
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true);
// THEN don't pause auth
assertFalse(mController.shouldPauseAuth());
}
@Test
public void testOverrideShouldPauseAuthOnShadeLocked() {
mController.onViewAttached();
captureStatusBarStateListeners();
captureAltAuthInterceptor();
sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
assertTrue(mController.shouldPauseAuth());
mAltAuthInterceptor.showAlternateAuthBouncer(); // force show
assertFalse(mController.shouldPauseAuth());
assertTrue(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
mAltAuthInterceptor.hideAlternateAuthBouncer(); // stop force show
assertTrue(mController.shouldPauseAuth());
assertFalse(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
}
@Test
public void testOnDetachedStateReset() {
// GIVEN view is attached
mController.onViewAttached();
captureAltAuthInterceptor();
// WHEN view is detached
mController.onViewDetached();
// THEN remove alternate auth interceptor
verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor);
}
@Test
public void testHiddenUdfpsBouncerOnTouchOutside_nothingHappens() {
// GIVEN view is attached
mController.onViewAttached();
captureAltAuthInterceptor();
// GIVEN udfps bouncer isn't showing
mAltAuthInterceptor.hideAlternateAuthBouncer();
// WHEN touch is observed outside the view
mController.onTouchOutsideView();
// THEN bouncer / alt auth methods are never called
verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
}
@Test
public void testShowingUdfpsBouncerOnTouchOutsideWithinThreshold_nothingHappens() {
// GIVEN view is attached
mController.onViewAttached();
captureAltAuthInterceptor();
// GIVEN udfps bouncer is showing
mAltAuthInterceptor.showAlternateAuthBouncer();
// WHEN touch is observed outside the view 200ms later (just within threshold)
mSystemClock.advanceTime(200);
mController.onTouchOutsideView();
// THEN bouncer / alt auth methods are never called because not enough time has passed
verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
}
@Test
public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer() {
// GIVEN view is attached
mController.onViewAttached();
captureAltAuthInterceptor();
// GIVEN udfps bouncer is showing
mAltAuthInterceptor.showAlternateAuthBouncer();
// WHEN touch is observed outside the view 205ms later
mSystemClock.advanceTime(205);
mController.onTouchOutsideView();
// THEN show the bouncer and reset alt auth
verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean());
}
@Test
public void testFadeInWithStatusBarExpansion() {
// GIVEN view is attached
mController.onViewAttached();
captureExpansionListeners();
captureKeyguardStateControllerCallback();
reset(mView);
// WHEN status bar expansion is 0
updateStatusBarExpansion(0, true);
// THEN alpha is 0
verify(mView).setUnpausedAlpha(0);
}
@Test
public void testShowUdfpsBouncer() {
// GIVEN view is attached and status bar expansion is 0
mController.onViewAttached();
captureExpansionListeners();
captureKeyguardStateControllerCallback();
captureAltAuthInterceptor();
updateStatusBarExpansion(0, true);
reset(mView);
when(mView.getContext()).thenReturn(mResourceContext);
when(mResourceContext.getString(anyInt())).thenReturn("test string");
// WHEN status bar expansion is 0 but udfps bouncer is requested
mAltAuthInterceptor.showAlternateAuthBouncer();
// THEN alpha is 255
verify(mView).setUnpausedAlpha(255);
}
@Test
public void testTransitionToFullShadeProgress() {
// GIVEN view is attached and status bar expansion is 1f
mController.onViewAttached();
captureExpansionListeners();
updateStatusBarExpansion(1f, true);
reset(mView);
// WHEN we're transitioning to the full shade
float transitionProgress = .6f;
mController.setTransitionToFullShadeProgress(transitionProgress);
// THEN alpha is between 0 and 255
verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255));
}
@Test
public void testShowUdfpsBouncer_transitionToFullShadeProgress() {
// GIVEN view is attached and status bar expansion is 1f
mController.onViewAttached();
captureExpansionListeners();
captureKeyguardStateControllerCallback();
captureAltAuthInterceptor();
updateStatusBarExpansion(1f, true);
mAltAuthInterceptor.showAlternateAuthBouncer();
reset(mView);
// WHEN we're transitioning to the full shade
mController.setTransitionToFullShadeProgress(1.0f);
// THEN alpha is 255 (b/c udfps bouncer is requested)
verify(mView).setUnpausedAlpha(255);
}
private void sendStatusBarStateChanged(int statusBarState) {
mStatusBarStateListener.onStateChanged(statusBarState);
}
private void captureStatusBarStateListeners() {
verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
mStatusBarStateListener = mStateListenerCaptor.getValue();
}
private void captureExpansionListeners() {
verify(mStatusBar, times(2))
.addExpansionChangedListener(mExpansionListenerCaptor.capture());
// first (index=0) is from super class, UdfpsAnimationViewController.
// second (index=1) is from UdfpsKeyguardViewController
mExpansionListeners = mExpansionListenerCaptor.getAllValues();
}
private void updateStatusBarExpansion(float expansion, boolean expanded) {
for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) {
listener.onExpansionChanged(expansion, expanded);
}
}
private void captureAltAuthInterceptor() {
verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
mAltAuthInterceptorCaptor.capture());
mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
}
private void captureKeyguardStateControllerCallback() {
verify(mKeyguardStateController).addCallback(
mKeyguardStateControllerCallbackCaptor.capture());
mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
}
}