Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c1d0d230 authored by Milton Wu's avatar Milton Wu Committed by Android (Google) Code Review
Browse files

Merge "Retry fingerprint or face unlock" into tm-qpr-dev

parents 062b18c7 5a47cf56
Loading
Loading
Loading
Loading
+88 −20
Original line number Diff line number Diff line
@@ -28,11 +28,13 @@ import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;

import com.android.settings.R;
@@ -50,11 +52,16 @@ import com.android.settingslib.transition.SettingsTransitionHelper;
 */
public abstract class BiometricsSettingsBase extends DashboardFragment {

    private static final int CONFIRM_REQUEST = 2001;
    @VisibleForTesting
    static final int CONFIRM_REQUEST = 2001;
    private static final int CHOOSE_LOCK_REQUEST = 2002;

    private static final String SAVE_STATE_CONFIRM_CREDETIAL = "confirm_credential";
    private static final String DO_NOT_FINISH_ACTIVITY = "do_not_finish_activity";
    @VisibleForTesting
    static final String RETRY_PREFERENCE_KEY = "retry_preference_key";
    @VisibleForTesting
    static final String RETRY_PREFERENCE_BUNDLE = "retry_preference_bundle";

    protected int mUserId;
    protected long mGkPwHandle;
@@ -63,6 +70,8 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
    @Nullable private FingerprintManager mFingerprintManager;
    // Do not finish() if choosing/confirming credential, or showing fp/face settings
    private boolean mDoNotFinishActivity;
    @Nullable private String mRetryPreferenceKey = null;
    @Nullable private Bundle mRetryPreferenceExtra = null;

    @Override
    public void onAttach(Context context) {
@@ -84,6 +93,8 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
        if (savedInstanceState != null) {
            mConfirmCredential = savedInstanceState.getBoolean(SAVE_STATE_CONFIRM_CREDETIAL);
            mDoNotFinishActivity = savedInstanceState.getBoolean(DO_NOT_FINISH_ACTIVITY);
            mRetryPreferenceKey = savedInstanceState.getString(RETRY_PREFERENCE_KEY);
            mRetryPreferenceExtra = savedInstanceState.getBundle(RETRY_PREFERENCE_BUNDLE);
            if (savedInstanceState.containsKey(
                    ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE)) {
                mGkPwHandle = savedInstanceState.getLong(
@@ -124,8 +135,7 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
        }
    }

    @Override
    public boolean onPreferenceTreeClick(Preference preference) {
    private boolean onRetryPreferenceTreeClick(Preference preference, final boolean retry) {
        final String key = preference.getKey();
        final Context context = requireActivity().getApplicationContext();

@@ -134,31 +144,77 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
        if (getFacePreferenceKey().equals(key)) {
            mDoNotFinishActivity = true;
            mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
                final byte[] token = BiometricUtils.requestGatekeeperHat(context, mGkPwHandle,
                        mUserId, challenge);
                try {
                    final byte[] token = requestGatekeeperHat(context, mGkPwHandle, mUserId,
                            challenge);
                    final Bundle extras = preference.getExtras();
                    extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
                    extras.putInt(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId);
                    extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
                    super.onPreferenceTreeClick(preference);
                } catch (IllegalStateException e) {
                    if (retry) {
                        mRetryPreferenceKey = preference.getKey();
                        mRetryPreferenceExtra = preference.getExtras();
                        mConfirmCredential = true;
                        launchChooseOrConfirmLock();
                    } else {
                        Log.e(getLogTag(), "face generateChallenge fail", e);
                        mDoNotFinishActivity = false;
                    }
                }
            });

            return true;
        } else if (getFingerprintPreferenceKey().equals(key)) {
            mDoNotFinishActivity = true;
            mFingerprintManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
                final byte[] token = BiometricUtils.requestGatekeeperHat(context, mGkPwHandle,
                        mUserId, challenge);
                try {
                    final byte[] token = requestGatekeeperHat(context, mGkPwHandle, mUserId,
                            challenge);
                    final Bundle extras = preference.getExtras();
                    extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
                    extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
                    super.onPreferenceTreeClick(preference);
                } catch (IllegalStateException e) {
                    if (retry) {
                        mRetryPreferenceKey = preference.getKey();
                        mRetryPreferenceExtra = preference.getExtras();
                        mConfirmCredential = true;
                        launchChooseOrConfirmLock();
                    } else {
                        Log.e(getLogTag(), "fingerprint generateChallenge fail", e);
                        mDoNotFinishActivity = false;
                    }
                }
            });

            return true;
        }
        return false;
    }

    @VisibleForTesting
    protected byte[] requestGatekeeperHat(@NonNull Context context, long gkPwHandle, int userId,
            long challenge) {
        return BiometricUtils.requestGatekeeperHat(context, gkPwHandle, userId, challenge);
    }

    @Override
    public boolean onPreferenceTreeClick(Preference preference) {
        return onRetryPreferenceTreeClick(preference, true)
                || super.onPreferenceTreeClick(preference);
    }

        return super.onPreferenceTreeClick(preference);
    private void retryPreferenceKey(@NonNull String key, @Nullable Bundle extras) {
        final Preference preference = findPreference(key);
        if (preference == null) {
            Log.w(getLogTag(), ".retryPreferenceKey, fail to find " + key);
            return;
        }

        if (extras != null) {
            preference.getExtras().putAll(extras);
        }
        onRetryPreferenceTreeClick(preference, false);
    }

    @Override
@@ -169,6 +225,10 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
        if (mGkPwHandle != 0L) {
            outState.putLong(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, mGkPwHandle);
        }
        if (!TextUtils.isEmpty(mRetryPreferenceKey)) {
            outState.putString(RETRY_PREFERENCE_KEY, mRetryPreferenceKey);
            outState.putBundle(RETRY_PREFERENCE_BUNDLE, mRetryPreferenceExtra);
        }
    }

    @Override
@@ -180,6 +240,11 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
            if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
                if (BiometricUtils.containsGatekeeperPasswordHandle(data)) {
                    mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
                    if (!TextUtils.isEmpty(mRetryPreferenceKey)) {
                        getActivity().overridePendingTransition(R.anim.sud_slide_next_in,
                                R.anim.sud_slide_next_out);
                        retryPreferenceKey(mRetryPreferenceKey, mRetryPreferenceExtra);
                    }
                } else {
                    Log.d(getLogTag(), "Data null or GK PW missing.");
                    finish();
@@ -188,6 +253,8 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
                Log.d(getLogTag(), "Password not confirmed.");
                finish();
            }
            mRetryPreferenceKey = null;
            mRetryPreferenceExtra = null;
        }
    }

@@ -211,7 +278,8 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
     */
    public abstract String getUseInAppsPreferenceKey();

    private void launchChooseOrConfirmLock() {
    @VisibleForTesting
    protected void launchChooseOrConfirmLock() {
        final ChooseLockSettingsHelper.Builder builder =
                new ChooseLockSettingsHelper.Builder(getActivity(), this)
                        .setRequestCode(CONFIRM_REQUEST)
+358 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.settings.biometrics.combination;

import static com.android.settings.biometrics.combination.BiometricsSettingsBase.CONFIRM_REQUEST;
import static com.android.settings.password.ChooseLockPattern.RESULT_FINISHED;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.content.Intent;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.XmlRes;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;

import com.android.settings.R;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settings.testutils.shadow.ShadowSettingsPreferenceFragment;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.android.settingslib.core.AbstractPreferenceController;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowSettingsPreferenceFragment.class, ShadowUtils.class, ShadowFragment.class})
public class CombinedBiometricProfileSettingsTest {

    private TestCombinedBiometricProfileSettings mFragment;
    private Context mContext;

    @Rule
    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Captor
    private ArgumentCaptor<Preference> mPreferenceCaptor;
    @Mock
    private FingerprintManager mFingerprintManager;
    @Mock
    private BiometricSettingsAppPreferenceController mBiometricSettingsAppPreferenceController;
    @Mock
    private FaceManager mFaceManager;

    @Before
    public void setUp() {
        ShadowUtils.setFingerprintManager(mFingerprintManager);
        ShadowUtils.setFaceManager(mFaceManager);
        FakeFeatureFactory.setupForTest();

        FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class,
                new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L)).get();
        mContext = spy(ApplicationProvider.getApplicationContext());
        mFragment = spy(new TestCombinedBiometricProfileSettings(mContext));
        doReturn(activity).when(mFragment).getActivity();

        ReflectionHelpers.setField(mFragment, "mDashboardFeatureProvider",
                FakeFeatureFactory.setupForTest().dashboardFeatureProvider);

        final Map<Class<?>, List<AbstractPreferenceController>> preferenceControllers =
                ReflectionHelpers.getField(mFragment, "mPreferenceControllers");
        List<AbstractPreferenceController> controllerList = new ArrayList<>();
        controllerList.add(mBiometricSettingsAppPreferenceController);
        preferenceControllers.put(BiometricSettingsAppPreferenceController.class, controllerList);

        doAnswer(invocation -> {
            final CharSequence key = invocation.getArgument(0);
            final Preference preference = new Preference(mContext);
            preference.setKey(key.toString());
            return preference;
        }).when(mFragment).findPreference(any());
    }

    @Test
    public void testClickFingerprintUnlockWithValidGkPwHandle() {
        doAnswer(invocation -> {
            final FingerprintManager.GenerateChallengeCallback callback =
                    invocation.getArgument(1);
            callback.onChallengeGenerated(0, 0, 1L);
            return null;
        }).when(mFingerprintManager).generateChallenge(anyInt(), any());
        doReturn(new byte[] { 1 }).when(mFragment).requestGatekeeperHat(any(), anyLong(), anyInt(),
                anyLong());

        // Start fragment
        mFragment.onAttach(mContext);
        mFragment.onCreate(null);
        mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY);
        mFragment.onResume();

        // User clicks on "Fingerprint Unlock"
        final Preference preference = new Preference(mContext);
        preference.setKey(mFragment.getFingerprintPreferenceKey());
        mFragment.onPreferenceTreeClick(preference);

        verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick(
                mPreferenceCaptor.capture());
        List<Preference> capturedPreferences = mPreferenceCaptor.getAllValues();

        assertThat(capturedPreferences.size()).isEqualTo(1);
        assertThat(capturedPreferences.get(0).getKey())
                .isEqualTo(mFragment.getFingerprintPreferenceKey());
    }

    @Test
    public void testClickFingerprintUnlockIfGkPwHandleTimeout() {
        doAnswer(invocation -> {
            final FingerprintManager.GenerateChallengeCallback callback =
                    invocation.getArgument(1);
            callback.onChallengeGenerated(0, 0, 1L);
            return null;
        }).when(mFingerprintManager).generateChallenge(anyInt(), any());
        doThrow(new IllegalStateException("Test")).when(mFragment).requestGatekeeperHat(any(),
                anyLong(), anyInt(), anyLong());

        // Start fragment
        mFragment.onAttach(mContext);
        mFragment.onCreate(null);
        mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY);
        mFragment.onResume();

        // User clicks on "Fingerprint Unlock"
        final Preference preference = new Preference(mContext);
        preference.setKey(mFragment.getFingerprintPreferenceKey());
        mFragment.onPreferenceTreeClick(preference);

        verify(mFragment).launchChooseOrConfirmLock();
    }

    @Test
    public void testActivityResultLaunchFingerprintUnlock() {
        doAnswer(invocation -> {
            final FingerprintManager.GenerateChallengeCallback callback =
                    invocation.getArgument(1);
            callback.onChallengeGenerated(0, 0, 1L);
            return null;
        }).when(mFingerprintManager).generateChallenge(anyInt(), any());
        doReturn(new byte[] { 1 }).when(mFragment).requestGatekeeperHat(any(), anyLong(), anyInt(),
                anyLong());

        // Start fragment
        mFragment.onAttach(mContext);
        final Bundle bundle = new Bundle();
        bundle.putString(BiometricsSettingsBase.RETRY_PREFERENCE_KEY,
                mFragment.getFingerprintPreferenceKey());
        final Bundle retryBundle = new Bundle();
        bundle.putBundle(BiometricsSettingsBase.RETRY_PREFERENCE_BUNDLE, retryBundle);
        mFragment.onCreate(bundle);
        mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY);
        mFragment.onResume();

        // onActivityResult
        mFragment.onActivityResult(CONFIRM_REQUEST, RESULT_FINISHED,
                new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L));

        verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick(
                mPreferenceCaptor.capture());
        List<Preference> capturedPreferences = mPreferenceCaptor.getAllValues();
        assertThat(capturedPreferences.size()).isEqualTo(1);
        assertThat(capturedPreferences.get(0).getKey())
                .isEqualTo(mFragment.getFingerprintPreferenceKey());
    }

    @Test
    public void testClickFaceUnlock() {
        doAnswer(invocation -> {
            final FaceManager.GenerateChallengeCallback callback =
                    invocation.getArgument(1);
            callback.onGenerateChallengeResult(0, 0, 1L);
            return null;
        }).when(mFaceManager).generateChallenge(anyInt(), any());
        doReturn(new byte[] { 1 }).when(mFragment).requestGatekeeperHat(any(), anyLong(), anyInt(),
                anyLong());

        // Start fragment
        mFragment.onAttach(mContext);
        mFragment.onCreate(null);
        mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY);
        mFragment.onResume();

        // User clicks on "Face Unlock"
        final Preference preference = new Preference(mContext);
        preference.setKey(mFragment.getFacePreferenceKey());
        mFragment.onPreferenceTreeClick(preference);

        verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick(
                mPreferenceCaptor.capture());
        List<Preference> capturedPreferences = mPreferenceCaptor.getAllValues();
        assertThat(capturedPreferences.size()).isEqualTo(1);
        assertThat(capturedPreferences.get(0).getKey()).isEqualTo(mFragment.getFacePreferenceKey());
    }

    @Test
    public void testClickFaceUnlockIfGkPwHandleTimeout() {
        doAnswer(invocation -> {
            final FaceManager.GenerateChallengeCallback callback =
                    invocation.getArgument(1);
            callback.onGenerateChallengeResult(0, 0, 1L);
            return null;
        }).when(mFaceManager).generateChallenge(anyInt(), any());
        doThrow(new IllegalStateException("Test")).when(mFragment).requestGatekeeperHat(any(),
                anyLong(), anyInt(), anyLong());

        // Start fragment
        mFragment.onAttach(mContext);
        mFragment.onCreate(null);
        mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY);
        mFragment.onResume();

        // User clicks on "Face Unlock"
        final Preference preference = new Preference(mContext);
        preference.setKey(mFragment.getFacePreferenceKey());
        mFragment.onPreferenceTreeClick(preference);

        verify(mFragment).launchChooseOrConfirmLock();
    }

    @Test
    public void testActivityResultLaunchFaceUnlock() {
        doAnswer(invocation -> {
            final FaceManager.GenerateChallengeCallback callback =
                    invocation.getArgument(1);
            callback.onGenerateChallengeResult(0, 0, 1L);
            return null;
        }).when(mFaceManager).generateChallenge(anyInt(), any());
        doReturn(new byte[] { 1 }).when(mFragment).requestGatekeeperHat(any(), anyLong(), anyInt(),
                anyLong());

        // Start fragment
        mFragment.onAttach(mContext);
        final Bundle bundle = new Bundle();
        bundle.putString(BiometricsSettingsBase.RETRY_PREFERENCE_KEY,
                mFragment.getFingerprintPreferenceKey());
        final Bundle retryBundle = new Bundle();
        bundle.putBundle(BiometricsSettingsBase.RETRY_PREFERENCE_BUNDLE, retryBundle);
        mFragment.onCreate(bundle);
        mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY);
        mFragment.onResume();

        // User clicks on "Face Unlock"
        final Preference preference = new Preference(mContext);
        preference.setKey(mFragment.getFacePreferenceKey());
        mFragment.onPreferenceTreeClick(preference);

        verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick(
                mPreferenceCaptor.capture());
        List<Preference> capturedPreferences = mPreferenceCaptor.getAllValues();
        assertThat(capturedPreferences.size()).isEqualTo(1);
        assertThat(capturedPreferences.get(0).getKey()).isEqualTo(mFragment.getFacePreferenceKey());
    }

    /**
     * a test fragment that initializes PreferenceScreen for testing.
     */
    static class TestCombinedBiometricProfileSettings extends CombinedBiometricProfileSettings {

        private final Context mContext;
        private final PreferenceManager mPreferenceManager;

        TestCombinedBiometricProfileSettings(Context context) {
            super();
            mContext = context;
            mPreferenceManager = new PreferenceManager(context);
            mPreferenceManager.setPreferences(mPreferenceManager.createPreferenceScreen(context));
            setArguments(new Bundle());
        }

        @Override
        public int getMetricsCategory() {
            return 0;
        }

        @Override
        public int getPreferenceScreenResId() {
            return R.xml.placeholder_prefs;
        }

        @Override
        public PreferenceScreen getPreferenceScreen() {
            return mPreferenceManager.getPreferenceScreen();
        }

        @Override
        public PreferenceManager getPreferenceManager() {
            return mPreferenceManager;
        }

        @Override
        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            // do nothing
        }

        @Override
        public void addPreferencesFromResource(@XmlRes int preferencesResId) {
            // do nothing
        }

        @Override
        public Context getContext() {
            return mContext;
        }

        @Override
        protected void launchChooseOrConfirmLock() {
            // do nothing
        }
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.hardware.face.FaceManager;
import android.os.UserManager;
import android.provider.Settings;

import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedSwitchPreference;
@@ -65,6 +66,9 @@ public class FaceSettingsLockscreenBypassPreferenceControllerTest {
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        ShadowUtils.setFaceManager(mFaceManager);
        FakeFeatureFactory.setupForTest();

        mContext = spy(RuntimeEnvironment.application);
        when(mContext.getSystemService(eq(Context.FACE_SERVICE))).thenReturn(mFaceManager);
        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+11 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.UserHandle;
import android.os.UserManager;
@@ -39,6 +40,7 @@ import java.util.Map;
public class ShadowUtils {

    private static FingerprintManager sFingerprintManager = null;
    private static FaceManager sFaceManager = null;
    private static boolean sIsUserAMonkey;
    private static boolean sIsDemoUser;
    private static ComponentName sDeviceOwnerComponentName;
@@ -63,6 +65,15 @@ public class ShadowUtils {
        sFingerprintManager = fingerprintManager;
    }

    @Implementation
    protected static FaceManager getFaceManagerOrNull(Context context) {
        return sFaceManager;
    }

    public static void setFaceManager(FaceManager faceManager) {
        sFaceManager = faceManager;
    }

    public static void reset() {
        sFingerprintManager = null;
        sIsUserAMonkey = false;