/* * Copyright (C) 2021 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.server.policy; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.PackageManager; import android.hardware.biometrics.BiometricStateListener; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.os.Handler; import android.os.PowerManager; import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableResources; import android.view.Window; import androidx.test.InstrumentationRegistry; import com.android.internal.R; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.List; /** * Unit tests for {@link SideFpsEventHandler}. * *

Run with atest SideFpsEventHandlerTest. */ @SmallTest @RunWith(AndroidTestingRunner.class) public class SideFpsEventHandlerTest { private static final List sAllStates = List.of( BiometricStateListener.STATE_IDLE, BiometricStateListener.STATE_ENROLLING, BiometricStateListener.STATE_KEYGUARD_AUTH, BiometricStateListener.STATE_BP_AUTH, BiometricStateListener.STATE_AUTH_OTHER); private static final Integer AUTO_DISMISS_DIALOG = 500; @Rule public TestableContext mContext = new TestableContext(InstrumentationRegistry.getContext(), null); @Mock private PackageManager mPackageManager; @Mock private FingerprintManager mFingerprintManager; @Mock private SideFpsToast mDialog; @Mock private Window mWindow; private final TestLooper mLooper = new TestLooper(); private SideFpsEventHandler mEventHandler; private BiometricStateListener mBiometricStateListener; @Before public void setup() { MockitoAnnotations.initMocks(this); mContext.addMockSystemService(PackageManager.class, mPackageManager); mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager); TestableResources resources = mContext.getOrCreateTestableResources(); resources.addOverride(R.integer.config_sideFpsToastTimeout, AUTO_DISMISS_DIALOG); when(mDialog.getWindow()).thenReturn(mWindow); mEventHandler = new SideFpsEventHandler( mContext, new Handler(mLooper.getLooper()), mContext.getSystemService(PowerManager.class), (ctx) -> mDialog); } @Test public void ignoresWithoutFingerprintFeature() { when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT))) .thenReturn(false); assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isFalse(); mLooper.dispatchAll(); verify(mDialog, never()).show(); } @Test public void ignoresWithoutSfps() throws Exception { setupWithSensor(false /* hasSfps */, true /* initialized */); for (int state : sAllStates) { setBiometricState(state); assertThat(mEventHandler.shouldConsumeSinglePress(200L)).isFalse(); mLooper.dispatchAll(); verify(mDialog, never()).show(); } } @Test public void ignoresWhileWaitingForSfps() throws Exception { setupWithSensor(true /* hasSfps */, false /* initialized */); for (int state : sAllStates) { setBiometricState(state); assertThat(mEventHandler.shouldConsumeSinglePress(400L)).isFalse(); mLooper.dispatchAll(); verify(mDialog, never()).show(); } } @Test public void ignoresWhenIdleOrUnknown() throws Exception { setupWithSensor(true /* hasSidefps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_IDLE); assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse(); setBiometricState(BiometricStateListener.STATE_AUTH_OTHER); assertThat(mEventHandler.shouldConsumeSinglePress(90000L)).isFalse(); mLooper.dispatchAll(); verify(mDialog, never()).show(); } @Test public void ignoresOnKeyguard() throws Exception { setupWithSensor(true /* hasSfps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_KEYGUARD_AUTH); assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse(); mLooper.dispatchAll(); verify(mDialog, never()).show(); } @Test public void doesNotpromptWhenBPisActive() throws Exception { setupWithSensor(true /* hasSideFps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_BP_AUTH); assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); mLooper.dispatchAll(); verify(mDialog, never()).show(); } @Test public void promptsWhenEnrolling() throws Exception { setupWithSensor(true /* hasSfps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_ENROLLING); assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); mLooper.dispatchAll(); verify(mDialog).show(); } @Test public void dialogDismissesAfterTime() throws Exception { setupWithSensor(true /* hasSfps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_ENROLLING); when(mDialog.isShowing()).thenReturn(true); assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); mLooper.dispatchAll(); verify(mDialog).show(); mLooper.moveTimeForward(AUTO_DISMISS_DIALOG); mLooper.dispatchAll(); verify(mDialog).dismiss(); } @Test public void dialogDoesNotDismissOnSensorTouch() throws Exception { setupWithSensor(true /* hasSfps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_ENROLLING); when(mDialog.isShowing()).thenReturn(true); assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); mLooper.dispatchAll(); verify(mDialog).show(); mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); mLooper.moveTimeForward(AUTO_DISMISS_DIALOG - 1); mLooper.dispatchAll(); verify(mDialog, never()).dismiss(); } private void setBiometricState(@BiometricStateListener.State int newState) { if (mBiometricStateListener != null) { mBiometricStateListener.onStateChanged(newState); mLooper.dispatchAll(); } } private void setupWithSensor(boolean hasSfps, boolean initialized) throws Exception { when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT))) .thenReturn(true); when(mFingerprintManager.isPowerbuttonFps()).thenReturn(hasSfps); mEventHandler.onFingerprintSensorReady(); ArgumentCaptor fpCallbackCaptor = ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(fpCallbackCaptor.capture()); if (initialized) { fpCallbackCaptor .getValue() .onAllAuthenticatorsRegistered( List.of(mock(FingerprintSensorPropertiesInternal.class))); if (hasSfps) { ArgumentCaptor captor = ArgumentCaptor.forClass(BiometricStateListener.class); verify(mFingerprintManager).registerBiometricStateListener(captor.capture()); mBiometricStateListener = captor.getValue(); } } } }