| /* |
| * 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.server.biometrics.sensors.fingerprint.hidl; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.trust.TrustManager; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.hardware.fingerprint.FingerprintManager; |
| import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; |
| import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; |
| import android.hardware.fingerprint.FingerprintSensorProperties; |
| import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; |
| import android.hardware.fingerprint.IUdfpsOverlayController; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| import android.util.Slog; |
| import android.util.SparseBooleanArray; |
| |
| import com.android.internal.R; |
| import com.android.server.biometrics.sensors.AuthenticationConsumer; |
| import com.android.server.biometrics.sensors.BaseClientMonitor; |
| import com.android.server.biometrics.sensors.BiometricScheduler; |
| import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; |
| import com.android.server.biometrics.sensors.LockoutResetDispatcher; |
| import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback; |
| import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Random; |
| |
| /** |
| * A mockable/testable provider of the {@link android.hardware.biometrics.fingerprint.V2_3} HIDL |
| * interface. This class is intended simulate UDFPS logic for devices that do not have an actual |
| * fingerprint@2.3 HAL (where UDFPS starts to become supported) |
| * |
| * UDFPS "accept" can only happen within a set amount of time after a sensor authentication. This is |
| * specified by {@link MockHalResultController#AUTH_VALIDITY_MS}. Touches after this duration will |
| * be treated as "reject". |
| * |
| * This class provides framework logic to emulate, for testing only, the UDFPS functionalies below: |
| * |
| * 1) IF either A) the caller is keyguard, and the device is not in a trusted state (authenticated |
| * via biometric sensor or unlocked with a trust agent {@see android.app.trust.TrustManager}, OR |
| * B) the caller is not keyguard, and regardless of trusted state, AND (following applies to both |
| * (A) and (B) above) {@link FingerprintManager#onFingerDown(int, int, float, float)} is |
| * received, this class will respond with {@link AuthenticationCallback#onAuthenticationFailed()} |
| * after a tunable flat_time + variance_time. |
| * |
| * In the case above (1), callers must not receive a successful authentication event here because |
| * the sensor has not actually been authenticated. |
| * |
| * 2) IF A) the caller is keyguard and the device is not in a trusted state, OR B) the caller is not |
| * keyguard and regardless of trusted state, AND (following applies to both (A) and (B)) the |
| * sensor is touched and the fingerprint is accepted by the HAL, and then |
| * {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will |
| * respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} |
| * after a tunable flat_time + variance_time. Note that the authentication callback from the |
| * sensor is held until {@link FingerprintManager#onFingerDown(int, int, float, float)} is |
| * invoked. |
| * |
| * In the case above (2), callers can receive a successful authentication callback because the |
| * real sensor was authenticated. Note that even though the real sensor was touched, keyguard |
| * fingerprint authentication does not put keyguard into a trusted state because the |
| * authentication callback is held until onFingerDown was invoked. This allows callers such as |
| * keyguard to simulate a realistic path. |
| * |
| * 3) IF the caller is keyguard AND the device in a trusted state and then |
| * {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will |
| * respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} |
| * after a tunable flat_time + variance time. |
| * |
| * In the case above (3), since the device is already unlockable via trust agent, it's fine to |
| * simulate the successful auth path. Non-keyguard clients will fall into either (1) or (2) |
| * above. |
| * |
| * This class currently does not simulate false rejection. Conversely, this class relies on the |
| * real hardware sensor so does not affect false acceptance. |
| */ |
| @SuppressWarnings("deprecation") |
| public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManager.TrustListener { |
| |
| private static final String TAG = "Fingerprint21UdfpsMock"; |
| |
| // Secure setting integer. If true, the system will load this class to enable udfps testing. |
| public static final String CONFIG_ENABLE_TEST_UDFPS = |
| "com.android.server.biometrics.sensors.fingerprint.test_udfps.enable"; |
| // Secure setting integer. A fixed duration intended to simulate something like the duration |
| // required for image capture. |
| private static final String CONFIG_AUTH_DELAY_PT1 = |
| "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt1"; |
| // Secure setting integer. A fixed duration intended to simulate something like the duration |
| // required for template matching to complete. |
| private static final String CONFIG_AUTH_DELAY_PT2 = |
| "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt2"; |
| // Secure setting integer. A random value between [-randomness, randomness] will be added to the |
| // capture delay above for each accept/reject. |
| private static final String CONFIG_AUTH_DELAY_RANDOMNESS = |
| "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_randomness"; |
| |
| private static final int DEFAULT_AUTH_DELAY_PT1_MS = 300; |
| private static final int DEFAULT_AUTH_DELAY_PT2_MS = 400; |
| private static final int DEFAULT_AUTH_DELAY_RANDOMNESS_MS = 100; |
| |
| @NonNull private final TestableBiometricScheduler mScheduler; |
| @NonNull private final Handler mHandler; |
| @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties; |
| @NonNull private final MockHalResultController mMockHalResultController; |
| @NonNull private final TrustManager mTrustManager; |
| @NonNull private final SparseBooleanArray mUserHasTrust; |
| @NonNull private final Random mRandom; |
| @NonNull private final FakeRejectRunnable mFakeRejectRunnable; |
| @NonNull private final FakeAcceptRunnable mFakeAcceptRunnable; |
| @NonNull private final RestartAuthRunnable mRestartAuthRunnable; |
| |
| private static class TestableBiometricScheduler extends BiometricScheduler { |
| @NonNull private Fingerprint21UdfpsMock mFingerprint21; |
| |
| TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler, |
| @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { |
| super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher); |
| } |
| |
| void init(@NonNull Fingerprint21UdfpsMock fingerprint21) { |
| mFingerprint21 = fingerprint21; |
| } |
| } |
| |
| /** |
| * All of the mocking/testing should happen in here. This way we don't need to modify the |
| * {@link BaseClientMonitor} implementations and can run the |
| * real path there. |
| */ |
| private static class MockHalResultController extends HalResultController { |
| |
| // Duration for which a sensor authentication can be treated as UDFPS success. |
| private static final int AUTH_VALIDITY_MS = 10 * 1000; // 10 seconds |
| |
| static class LastAuthArgs { |
| @NonNull final AuthenticationConsumer lastAuthenticatedClient; |
| final long deviceId; |
| final int fingerId; |
| final int groupId; |
| @Nullable final ArrayList<Byte> token; |
| |
| LastAuthArgs(@NonNull AuthenticationConsumer authenticationConsumer, long deviceId, |
| int fingerId, int groupId, @Nullable ArrayList<Byte> token) { |
| lastAuthenticatedClient = authenticationConsumer; |
| this.deviceId = deviceId; |
| this.fingerId = fingerId; |
| this.groupId = groupId; |
| if (token == null) { |
| this.token = null; |
| } else { |
| // Store a copy so the owner can be GC'd |
| this.token = new ArrayList<>(token); |
| } |
| } |
| } |
| |
| // Initialized after the constructor, but before it's ever used. |
| @NonNull private RestartAuthRunnable mRestartAuthRunnable; |
| @NonNull private Fingerprint21UdfpsMock mFingerprint21; |
| @Nullable private LastAuthArgs mLastAuthArgs; |
| |
| MockHalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler, |
| @NonNull BiometricScheduler scheduler) { |
| super(sensorId, context, handler, scheduler); |
| } |
| |
| void init(@NonNull RestartAuthRunnable restartAuthRunnable, |
| @NonNull Fingerprint21UdfpsMock fingerprint21) { |
| mRestartAuthRunnable = restartAuthRunnable; |
| mFingerprint21 = fingerprint21; |
| } |
| |
| @Nullable AuthenticationConsumer getLastAuthenticatedClient() { |
| return mLastAuthArgs != null ? mLastAuthArgs.lastAuthenticatedClient : null; |
| } |
| |
| /** |
| * Intercepts the HAL authentication and holds it until the UDFPS simulation decides |
| * that authentication finished. |
| */ |
| @Override |
| public void onAuthenticated(long deviceId, int fingerId, int groupId, |
| ArrayList<Byte> token) { |
| mHandler.post(() -> { |
| final BaseClientMonitor client = mScheduler.getCurrentClient(); |
| if (!(client instanceof AuthenticationConsumer)) { |
| Slog.e(TAG, "Non authentication consumer: " + client); |
| return; |
| } |
| |
| final boolean accepted = fingerId != 0; |
| if (accepted) { |
| mFingerprint21.setDebugMessage("Finger accepted"); |
| } else { |
| mFingerprint21.setDebugMessage("Finger rejected"); |
| } |
| |
| final AuthenticationConsumer authenticationConsumer = |
| (AuthenticationConsumer) client; |
| mLastAuthArgs = new LastAuthArgs(authenticationConsumer, deviceId, fingerId, |
| groupId, token); |
| |
| // Remove any existing restart runnbables since auth just started, and put a new |
| // one on the queue. |
| mHandler.removeCallbacks(mRestartAuthRunnable); |
| mRestartAuthRunnable.setLastAuthReference(authenticationConsumer); |
| mHandler.postDelayed(mRestartAuthRunnable, AUTH_VALIDITY_MS); |
| }); |
| } |
| |
| /** |
| * Calls through to the real interface and notifies clients of accept/reject. |
| */ |
| void sendAuthenticated(long deviceId, int fingerId, int groupId, |
| ArrayList<Byte> token) { |
| Slog.d(TAG, "sendAuthenticated: " + (fingerId != 0)); |
| mFingerprint21.setDebugMessage("Udfps match: " + (fingerId != 0)); |
| super.onAuthenticated(deviceId, fingerId, groupId, token); |
| } |
| } |
| |
| public static Fingerprint21UdfpsMock newInstance(@NonNull Context context, |
| @NonNull FingerprintStateCallback fingerprintStateCallback, |
| @NonNull FingerprintSensorPropertiesInternal sensorProps, |
| @NonNull LockoutResetDispatcher lockoutResetDispatcher, |
| @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { |
| Slog.d(TAG, "Creating Fingerprint23Mock!"); |
| |
| final Handler handler = new Handler(Looper.getMainLooper()); |
| final TestableBiometricScheduler scheduler = |
| new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher); |
| final MockHalResultController controller = |
| new MockHalResultController(sensorProps.sensorId, context, handler, scheduler); |
| return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler, |
| handler, lockoutResetDispatcher, controller); |
| } |
| |
| private static abstract class FakeFingerRunnable implements Runnable { |
| private long mFingerDownTime; |
| private int mCaptureDuration; |
| |
| /** |
| * @param fingerDownTime System time when onFingerDown occurred |
| * @param captureDuration Duration that the finger needs to be down for |
| */ |
| void setSimulationTime(long fingerDownTime, int captureDuration) { |
| mFingerDownTime = fingerDownTime; |
| mCaptureDuration = captureDuration; |
| } |
| |
| @SuppressWarnings("BooleanMethodIsAlwaysInverted") |
| boolean isImageCaptureComplete() { |
| return System.currentTimeMillis() - mFingerDownTime > mCaptureDuration; |
| } |
| } |
| |
| private final class FakeRejectRunnable extends FakeFingerRunnable { |
| @Override |
| public void run() { |
| mMockHalResultController.sendAuthenticated(0, 0, 0, null); |
| } |
| } |
| |
| private final class FakeAcceptRunnable extends FakeFingerRunnable { |
| @Override |
| public void run() { |
| if (mMockHalResultController.mLastAuthArgs == null) { |
| // This can happen if the user has trust agents enabled, which make lockscreen |
| // dismissable. Send a fake non-zero (accept) finger. |
| Slog.d(TAG, "Sending fake finger"); |
| mMockHalResultController.sendAuthenticated(1 /* deviceId */, |
| 1 /* fingerId */, 1 /* groupId */, null /* token */); |
| } else { |
| mMockHalResultController.sendAuthenticated( |
| mMockHalResultController.mLastAuthArgs.deviceId, |
| mMockHalResultController.mLastAuthArgs.fingerId, |
| mMockHalResultController.mLastAuthArgs.groupId, |
| mMockHalResultController.mLastAuthArgs.token); |
| } |
| } |
| } |
| |
| /** |
| * The fingerprint HAL allows multiple (5) fingerprint attempts per HIDL invocation of the |
| * authenticate method. However, valid fingerprint authentications are invalidated after |
| * {@link MockHalResultController#AUTH_VALIDITY_MS}, meaning UDFPS touches will be reported as |
| * rejects if touched after that duration. However, since a valid fingerprint was detected, the |
| * HAL and FingerprintService will not look for subsequent fingerprints. |
| * |
| * In order to keep the FingerprintManager API consistent (that multiple fingerprint attempts |
| * are allowed per auth lifecycle), we internally cancel and restart authentication so that the |
| * sensor is responsive again. |
| */ |
| private static final class RestartAuthRunnable implements Runnable { |
| @NonNull private final Fingerprint21UdfpsMock mFingerprint21; |
| @NonNull private final TestableBiometricScheduler mScheduler; |
| |
| // Store a reference to the auth consumer that should be invalidated. |
| private AuthenticationConsumer mLastAuthConsumer; |
| |
| RestartAuthRunnable(@NonNull Fingerprint21UdfpsMock fingerprint21, |
| @NonNull TestableBiometricScheduler scheduler) { |
| mFingerprint21 = fingerprint21; |
| mScheduler = scheduler; |
| } |
| |
| void setLastAuthReference(AuthenticationConsumer lastAuthConsumer) { |
| mLastAuthConsumer = lastAuthConsumer; |
| } |
| |
| @Override |
| public void run() { |
| final BaseClientMonitor client = mScheduler.getCurrentClient(); |
| |
| // We don't care about FingerprintDetectClient, since accept/rejects are both OK. UDFPS |
| // rejects will just simulate the path where non-enrolled fingers are presented. |
| if (!(client instanceof FingerprintAuthenticationClient)) { |
| Slog.e(TAG, "Non-FingerprintAuthenticationClient client: " + client); |
| return; |
| } |
| |
| // Perhaps the runnable is stale, or the user stopped/started auth manually. Do not |
| // restart auth in this case. |
| if (client != mLastAuthConsumer) { |
| Slog.e(TAG, "Current client: " + client |
| + " does not match mLastAuthConsumer: " + mLastAuthConsumer); |
| return; |
| } |
| |
| Slog.d(TAG, "Restarting auth, current: " + client); |
| mFingerprint21.setDebugMessage("Auth timed out"); |
| |
| final FingerprintAuthenticationClient authClient = |
| (FingerprintAuthenticationClient) client; |
| // Store the authClient parameters so it can be rescheduled |
| final IBinder token = client.getToken(); |
| final long operationId = authClient.getOperationId(); |
| final int user = client.getTargetUserId(); |
| final int cookie = client.getCookie(); |
| final ClientMonitorCallbackConverter listener = client.getListener(); |
| final String opPackageName = client.getOwnerString(); |
| final boolean restricted = authClient.isRestricted(); |
| final int statsClient = client.getStatsClient(); |
| final boolean isKeyguard = authClient.isKeyguard(); |
| |
| // Don't actually send cancel() to the HAL, since successful auth already finishes |
| // HAL authenticate() lifecycle. Just |
| mScheduler.getInternalCallback().onClientFinished(client, true /* success */); |
| |
| // Schedule this only after we invoke onClientFinished for the previous client, so that |
| // internal preemption logic is not run. |
| mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token, |
| operationId, user, cookie, listener, opPackageName, restricted, statsClient, |
| isKeyguard); |
| } |
| } |
| |
| private Fingerprint21UdfpsMock(@NonNull Context context, |
| @NonNull FingerprintStateCallback fingerprintStateCallback, |
| @NonNull FingerprintSensorPropertiesInternal sensorProps, |
| @NonNull TestableBiometricScheduler scheduler, |
| @NonNull Handler handler, |
| @NonNull LockoutResetDispatcher lockoutResetDispatcher, |
| @NonNull MockHalResultController controller) { |
| super(context, fingerprintStateCallback, sensorProps, scheduler, handler, |
| lockoutResetDispatcher, controller); |
| mScheduler = scheduler; |
| mScheduler.init(this); |
| mHandler = handler; |
| // resetLockout is controlled by the framework, so hardwareAuthToken is not required |
| final boolean resetLockoutRequiresHardwareAuthToken = false; |
| final int maxTemplatesAllowed = mContext.getResources() |
| .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser); |
| mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId, |
| sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo, |
| FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, |
| resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations()); |
| mMockHalResultController = controller; |
| mUserHasTrust = new SparseBooleanArray(); |
| mTrustManager = context.getSystemService(TrustManager.class); |
| mTrustManager.registerTrustListener(this); |
| mRandom = new Random(); |
| mFakeRejectRunnable = new FakeRejectRunnable(); |
| mFakeAcceptRunnable = new FakeAcceptRunnable(); |
| mRestartAuthRunnable = new RestartAuthRunnable(this, mScheduler); |
| |
| // We can't initialize this during MockHalresultController's constructor due to a circular |
| // dependency. |
| mMockHalResultController.init(mRestartAuthRunnable, this); |
| } |
| |
| @Override |
| public void onTrustChanged(boolean enabled, int userId, int flags) { |
| mUserHasTrust.put(userId, enabled); |
| } |
| |
| @Override |
| public void onTrustManagedChanged(boolean enabled, int userId) { |
| |
| } |
| |
| @Override |
| public void onTrustError(CharSequence message) { |
| |
| } |
| |
| @Override |
| @NonNull |
| public List<FingerprintSensorPropertiesInternal> getSensorProperties() { |
| final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>(); |
| properties.add(mSensorProperties); |
| return properties; |
| } |
| |
| @Override |
| public void onPointerDown(int sensorId, int x, int y, float minor, float major) { |
| mHandler.post(() -> { |
| Slog.d(TAG, "onFingerDown"); |
| final AuthenticationConsumer lastAuthenticatedConsumer = |
| mMockHalResultController.getLastAuthenticatedClient(); |
| final BaseClientMonitor currentScheduledClient = mScheduler.getCurrentClient(); |
| |
| if (currentScheduledClient == null) { |
| Slog.d(TAG, "Not authenticating"); |
| return; |
| } |
| |
| mHandler.removeCallbacks(mFakeRejectRunnable); |
| mHandler.removeCallbacks(mFakeAcceptRunnable); |
| |
| // The sensor was authenticated, is still the currently scheduled client, and the |
| // user touched the UDFPS affordance. Pretend that auth succeeded. |
| final boolean authenticatedClientIsCurrent = lastAuthenticatedConsumer != null |
| && lastAuthenticatedConsumer == currentScheduledClient; |
| // User is unlocked on keyguard via Trust Agent |
| final boolean keyguardAndTrusted; |
| if (currentScheduledClient instanceof FingerprintAuthenticationClient) { |
| keyguardAndTrusted = ((FingerprintAuthenticationClient) currentScheduledClient) |
| .isKeyguard() |
| && mUserHasTrust.get(currentScheduledClient.getTargetUserId(), false); |
| } else { |
| keyguardAndTrusted = false; |
| } |
| |
| final int captureDuration = getNewCaptureDuration(); |
| final int matchingDuration = getMatchingDuration(); |
| final int totalDuration = captureDuration + matchingDuration; |
| setDebugMessage("Duration: " + totalDuration |
| + " (" + captureDuration + " + " + matchingDuration + ")"); |
| if (authenticatedClientIsCurrent || keyguardAndTrusted) { |
| mFakeAcceptRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration); |
| mHandler.postDelayed(mFakeAcceptRunnable, totalDuration); |
| } else if (currentScheduledClient instanceof AuthenticationConsumer) { |
| // Something is authenticating but authentication has not succeeded yet. Pretend |
| // that auth rejected. |
| mFakeRejectRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration); |
| mHandler.postDelayed(mFakeRejectRunnable, totalDuration); |
| } |
| }); |
| } |
| |
| @Override |
| public void onPointerUp(int sensorId) { |
| mHandler.post(() -> { |
| Slog.d(TAG, "onFingerUp"); |
| |
| // Only one of these can be on the handler at any given time (see onFingerDown). If |
| // image capture is not complete, send ACQUIRED_TOO_FAST and remove the runnable from |
| // the handler. Image capture (onFingerDown) needs to happen again. |
| if (mHandler.hasCallbacks(mFakeRejectRunnable) |
| && !mFakeRejectRunnable.isImageCaptureComplete()) { |
| mHandler.removeCallbacks(mFakeRejectRunnable); |
| mMockHalResultController.onAcquired(0 /* deviceId */, |
| FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST, |
| 0 /* vendorCode */); |
| } else if (mHandler.hasCallbacks(mFakeAcceptRunnable) |
| && !mFakeAcceptRunnable.isImageCaptureComplete()) { |
| mHandler.removeCallbacks(mFakeAcceptRunnable); |
| mMockHalResultController.onAcquired(0 /* deviceId */, |
| FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST, |
| 0 /* vendorCode */); |
| } |
| }); |
| } |
| |
| private int getNewCaptureDuration() { |
| final ContentResolver contentResolver = mContext.getContentResolver(); |
| final int captureTime = Settings.Secure.getIntForUser(contentResolver, |
| CONFIG_AUTH_DELAY_PT1, |
| DEFAULT_AUTH_DELAY_PT1_MS, |
| UserHandle.USER_CURRENT); |
| final int randomDelayRange = Settings.Secure.getIntForUser(contentResolver, |
| CONFIG_AUTH_DELAY_RANDOMNESS, |
| DEFAULT_AUTH_DELAY_RANDOMNESS_MS, |
| UserHandle.USER_CURRENT); |
| final int randomDelay = mRandom.nextInt(randomDelayRange * 2) - randomDelayRange; |
| |
| // Must be at least 0 |
| return Math.max(captureTime + randomDelay, 0); |
| } |
| |
| private int getMatchingDuration() { |
| final int matchingTime = Settings.Secure.getIntForUser(mContext.getContentResolver(), |
| CONFIG_AUTH_DELAY_PT2, |
| DEFAULT_AUTH_DELAY_PT2_MS, |
| UserHandle.USER_CURRENT); |
| |
| // Must be at least 0 |
| return Math.max(matchingTime, 0); |
| } |
| |
| private void setDebugMessage(String message) { |
| try { |
| final IUdfpsOverlayController controller = getUdfpsOverlayController(); |
| // Things can happen before SysUI loads and sets the controller. |
| if (controller != null) { |
| Slog.d(TAG, "setDebugMessage: " + message); |
| controller.setDebugMessage(mSensorProperties.sensorId, message); |
| } |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote exception when sending message: " + message, e); |
| } |
| } |
| } |