Merge "SideFPS UX updates" into tm-qpr-dev

This commit is contained in:
Joshua Mccloskey
2022-07-26 20:34:50 +00:00
committed by Android (Google) Code Review
29 changed files with 715 additions and 153 deletions

View File

@@ -156,6 +156,11 @@ public interface BiometricConstants {
*/
int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18;
/**
* A power press stopped this biometric operation.
* @hide
*/
int BIOMETRIC_ERROR_POWER_PRESSED = 19;
/**
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
* because the authentication attempt was unsuccessful.

View File

@@ -70,6 +70,7 @@ public interface BiometricFaceConstants {
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
BIOMETRIC_ERROR_RE_ENROLL,
FACE_ERROR_UNKNOWN,
BIOMETRIC_ERROR_POWER_PRESSED,
})
@Retention(RetentionPolicy.SOURCE)
@interface FaceError {}
@@ -183,6 +184,12 @@ public interface BiometricFaceConstants {
*/
int FACE_ERROR_UNKNOWN = 17;
/**
* A power press stopped this biometric operation.
* @hide
*/
int BIOMETRIC_ERROR_POWER_PRESSED = 19;
/**
* Vendor codes received from the HAL start at 0. Codes that the framework exposes to keyguard
* append this value for some reason. We should probably remove this and just send the actual

View File

@@ -61,7 +61,8 @@ public interface BiometricFingerprintConstants {
BIOMETRIC_ERROR_RE_ENROLL,
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
FINGERPRINT_ERROR_UNKNOWN,
FINGERPRINT_ERROR_BAD_CALIBRATION})
FINGERPRINT_ERROR_BAD_CALIBRATION,
BIOMETRIC_ERROR_POWER_PRESSED})
@Retention(RetentionPolicy.SOURCE)
@interface FingerprintError {}
@@ -187,6 +188,12 @@ public interface BiometricFingerprintConstants {
*/
int FINGERPRINT_ERROR_BAD_CALIBRATION = 18;
/**
* A power press stopped this biometric operation.
* @hide
*/
int BIOMETRIC_ERROR_POWER_PRESSED = 19;
/**
* @hide
*/

View File

@@ -46,6 +46,14 @@ public abstract class BiometricStateListener extends IBiometricStateListener.Stu
public @interface State {
}
// The sensor received a touch.
public static final int ACTION_SENSOR_TOUCH = 0;
@IntDef({ACTION_SENSOR_TOUCH})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
}
/**
* Defines behavior in response to state update
* @param newState new state of the biometric sensor
@@ -53,6 +61,13 @@ public abstract class BiometricStateListener extends IBiometricStateListener.Stu
public void onStateChanged(@BiometricStateListener.State int newState) {
}
/**
* Invoked when a biometric action has occurred.
*/
public void onBiometricAction(@BiometricStateListener.Action int action) {
}
/**
* Invoked when enrollment state changes for the specified user
*/

View File

@@ -22,5 +22,6 @@ package android.hardware.biometrics;
*/
oneway interface IBiometricStateListener {
void onStateChanged(int newState);
void onBiometricAction(int action);
void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments);
}

View File

@@ -101,6 +101,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
private static final int MSG_FINGERPRINT_DETECTED = 107;
private static final int MSG_UDFPS_POINTER_DOWN = 108;
private static final int MSG_UDFPS_POINTER_UP = 109;
private static final int MSG_POWER_BUTTON_PRESSED = 110;
/**
* @hide
@@ -983,6 +984,16 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
/**
* This is triggered by SideFpsEventHandler
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void onPowerPressed() {
Slog.i(TAG, "onPowerPressed");
mHandler.obtainMessage(MSG_POWER_BUTTON_PRESSED).sendToTarget();
}
/**
* Determine if there is at least one fingerprint enrolled.
*
@@ -1196,6 +1207,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
case MSG_UDFPS_POINTER_UP:
sendUdfpsPointerUp(msg.arg1 /* sensorId */);
break;
case MSG_POWER_BUTTON_PRESSED:
sendPowerPressed();
break;
default:
Slog.w(TAG, "Unknown message: " + msg.what);
@@ -1325,6 +1339,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
mAuthenticationCallback.onUdfpsPointerUp(sensorId);
}
private void sendPowerPressed() {
try {
mService.onPowerPressed();
} catch (RemoteException e) {
Slog.e(TAG, "Error sending power press", e);
}
}
/**
* @hide
*/
@@ -1461,6 +1483,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
case FINGERPRINT_ERROR_BAD_CALIBRATION:
return context.getString(
com.android.internal.R.string.fingerprint_error_bad_calibration);
case BIOMETRIC_ERROR_POWER_PRESSED:
return context.getString(
com.android.internal.R.string.fingerprint_error_power_pressed);
case FINGERPRINT_ERROR_VENDOR: {
String[] msgArray = context.getResources().getStringArray(
com.android.internal.R.array.fingerprint_error_vendor);

View File

@@ -171,4 +171,7 @@ interface IFingerprintService {
// Registers BiometricStateListener.
void registerBiometricStateListener(IBiometricStateListener listener);
// Sends a power button pressed event to all listeners.
oneway void onPowerPressed();
}

View File

@@ -9703,6 +9703,43 @@ public final class Settings {
@Readable
public static final String FACE_UNLOCK_RE_ENROLL = "face_unlock_re_enroll";
/**
* The time (in millis) to wait for a power button before sending a
* successful auth in to keyguard(for side fingerprint)
* @hide
*/
@Readable
public static final String FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW =
"fingerprint_side_fps_kg_power_window";
/**
* The time (in millis) to wait for a power button before sending
* a successful auth in biometric prompt(for side fingerprint)
* @hide
*/
@Readable
public static final String FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW =
"fingerprint_side_fps_bp_power_window";
/**
* The time (in millis) that a finger tap will wait for a power button
* before dismissing the power dialog during enrollment(for side
* fingerprint)
* @hide
*/
@Readable
public static final String FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW =
"fingerprint_side_fps_enroll_tap_window";
/**
* The time (in millis) that a power event will ignore future authentications
* (for side fingerprint)
* @hide
*/
@Readable
public static final String FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME =
"fingerprint_side_fps_auth_downtime";
/**
* Whether or not debugging is enabled.
* @hide

View File

@@ -3498,6 +3498,22 @@
<!-- Specify if the fingerprint hardware support gestures-->
<bool name="config_fingerprintSupportsGestures">false</bool>
<!-- The time (in millis) to wait for a power button before sending
a successful auth in biometric prompt(for side fingerprint) -->
<integer name="config_sidefpsBpPowerPressWindow">300</integer>
<!-- The time (in millis) to wait for a power button before sending a
successful auth in to keyguard(for side fingerprint) -->
<integer name="config_sidefpsKeyguardPowerPressWindow">300</integer>
<!-- The time (in millis) that a power event will ignore future authentications
(for side fingerprint) -->
<integer name="config_sidefpsPostAuthDowntime">400</integer>
<!-- The time (in millis) that a finger tap will wait for a power button
before dismissing the power dialog during enrollment(for side fingerprint) -->
<integer name="config_sidefpsEnrollPowerPressWindow">300</integer>
<!-- This config is used to force VoiceInteractionService to start on certain low ram devices.
It declares the package name of VoiceInteractionService that should be started. -->
<string translatable="false" name="config_forceVoiceInteractionServicePackage"></string>

View File

@@ -1751,6 +1751,8 @@
<string name="fingerprint_error_security_update_required">Sensor temporarily disabled.</string>
<!-- Generic error message shown when fingerprint needs calibration [CHAR LIMIT=150] -->
<string name="fingerprint_error_bad_calibration">Can\u2019t use fingerprint sensor. Visit a repair provider</string>
<!-- Generic error message shown when the power button has been pressed. [CHAR LIMIT=150] -->
<string name="fingerprint_error_power_pressed">Power button pressed</string>
<!-- Template to be used to name enrolled fingerprints by default. -->
<string name="fingerprint_name_template">Finger <xliff:g id="fingerId" example="1">%d</xliff:g></string>

View File

@@ -2622,10 +2622,15 @@
<java-symbol type="string" name="fingerprint_recalibrate_notification_name" />
<java-symbol type="string" name="fingerprint_recalibrate_notification_title" />
<java-symbol type="string" name="fingerprint_recalibrate_notification_content" />
<java-symbol type="string" name="fingerprint_error_power_pressed" />
<!-- Fingerprint config -->
<java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/>
<java-symbol type="bool" name="config_fingerprintSupportsGestures"/>
<java-symbol type="integer" name="config_sidefpsBpPowerPressWindow"/>
<java-symbol type="integer" name="config_sidefpsKeyguardPowerPressWindow"/>
<java-symbol type="integer" name="config_sidefpsPostAuthDowntime"/>
<java-symbol type="integer" name="config_sidefpsEnrollPowerPressWindow"/>
<!-- Face authentication messages -->
<java-symbol type="string" name="face_recalibrate_notification_name" />

View File

@@ -118,6 +118,10 @@ public class SecureSettings {
Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD,
Settings.Secure.FACE_UNLOCK_APP_ENABLED,
Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,

View File

@@ -172,6 +172,11 @@ public class SecureSettingsValidators {
Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);

View File

@@ -805,8 +805,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private Runnable mRetryFingerprintAuthentication = new Runnable() {
@Override
public void run() {
Log.w(TAG, "Retrying fingerprint after HW unavailable, attempt " +
mHardwareFingerprintUnavailableRetryCount);
Log.w(TAG,
"Retrying fingerprint attempt: " + mHardwareFingerprintUnavailableRetryCount);
if (mFpm.isHardwareDetected()) {
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
@@ -833,7 +833,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
|| msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
Log.d(TAG, "Fingerprint retrying auth due to(" + msgId + ") -> " + errString);
mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
}

View File

@@ -108,6 +108,17 @@ public class BiometricStateCallback implements ClientMonitorCallback {
}
}
@Override
public void onBiometricAction(@BiometricStateListener.Action int action) {
for (IBiometricStateListener listener : mBiometricStateListeners) {
try {
listener.onBiometricAction(action);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in onBiometricAction", e);
}
}
}
/**
* This should be invoked when:
* 1) Enrolled --> None-enrolled

View File

@@ -30,6 +30,11 @@ public interface ClientMonitorCallback {
*/
default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {}
/**
* Invoked when a biometric action has occurred.
*/
default void onBiometricAction(int action) {}
/**
* Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous
* (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge,

View File

@@ -43,6 +43,13 @@ public class ClientMonitorCompositeCallback implements ClientMonitorCallback {
}
}
@Override
public final void onBiometricAction(int action) {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onBiometricAction(action);
}
}
@Override
public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {

View File

@@ -1020,9 +1020,18 @@ public class FingerprintService extends SystemService {
@Override
public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
FingerprintService.this.registerBiometricStateListener(listener);
}
}
@Override
public void onPowerPressed() {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
for (ServiceProvider provider : mServiceProviders) {
provider.onPowerPressed();
}
}
};
public FingerprintService(Context context) {
super(context);

View File

@@ -0,0 +1,27 @@
/*
* 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.server.biometrics.sensors.fingerprint;
/**
* Interface for handling power presses.
*/
public interface PowerPressHandler {
/**
* Indicates a power press has occurred.
*/
void onPowerPressed();
}

View File

@@ -150,6 +150,8 @@ public interface ServiceProvider {
void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
void onPowerPressed();
/**
* Sets side-fps controller
* @param controller side-fps controller

View File

@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.app.TaskStackListener;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.common.ICancellationSignal;
@@ -29,10 +30,15 @@ import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -46,17 +52,18 @@ import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import java.util.ArrayList;
import java.util.function.Supplier;
/**
* Fingerprint-specific authentication client supporting the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
* Fingerprint-specific authentication client supporting the {@link
* android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> implements
Udfps, LockoutConsumer {
class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
@NonNull private final LockoutCache mLockoutCache;
@@ -64,33 +71,88 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
@NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
@NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
@Nullable private ICancellationSignal mCancellationSignal;
@Nullable
private ICancellationSignal mCancellationSignal;
private boolean mIsPointerDown;
private final Handler mHandler;
FingerprintAuthenticationClient(@NonNull Context context,
private static final int MESSAGE_IGNORE_AUTH = 1;
private static final int MESSAGE_AUTH_SUCCESS = 2;
private long mWaitForAuthKeyguard;
private long mWaitForAuthBp;
private long mIgnoreAuthFor;
FingerprintAuthenticationClient(
@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
@NonNull IBinder token,
long requestId,
@NonNull ClientMonitorCallbackConverter listener,
int targetUserId,
long operationId,
boolean restricted,
@NonNull String owner,
int cookie,
boolean requireConfirmation,
int sensorId,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@NonNull BiometricLogger biometricLogger,
@NonNull BiometricContext biometricContext,
boolean isStrongBiometric,
@Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
@Nullable TaskStackListener taskStackListener,
@NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
cookie, requireConfirmation, sensorId,
biometricLogger, biometricContext,
isStrongBiometric, taskStackListener,
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull Handler handler) {
super(
context,
lazyDaemon,
token,
listener,
targetUserId,
operationId,
restricted,
owner,
cookie,
requireConfirmation,
sensorId,
biometricLogger,
biometricContext,
isStrongBiometric,
taskStackListener,
lockoutCache,
allowBackgroundAuthentication,
true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutCache = lockoutCache;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */);
mHandler = handler;
mWaitForAuthKeyguard =
context.getResources()
.getInteger(R.integer.config_sidefpsKeyguardPowerPressWindow);
mWaitForAuthBp =
context.getResources().getInteger(R.integer.config_sidefpsBpPowerPressWindow);
mIgnoreAuthFor =
context.getResources().getInteger(R.integer.config_sidefpsPostAuthDowntime);
if (mSensorProps.isAnySidefpsType()) {
if (Build.isDebuggable()) {
mWaitForAuthKeyguard = Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW,
(int) mWaitForAuthKeyguard, UserHandle.USER_CURRENT);
mWaitForAuthBp = Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, (int) mWaitForAuthBp,
UserHandle.USER_CURRENT);
mIgnoreAuthFor = Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, (int) mIgnoreAuthFor,
UserHandle.USER_CURRENT);
}
}
}
@Override
@@ -108,8 +170,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
return new ClientMonitorCompositeCallback(mALSProbeCallback,
getBiometricContextUnsubscriber(), callback);
return new ClientMonitorCompositeCallback(
mALSProbeCallback, getBiometricContextUnsubscriber(), callback);
}
@Override
@@ -126,16 +188,37 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
}
@Override
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
boolean authenticated, ArrayList<Byte> token) {
super.onAuthenticated(identifier, authenticated, token);
public void onAuthenticated(
BiometricAuthenticator.Identifier identifier,
boolean authenticated,
ArrayList<Byte> token) {
if (authenticated) {
mState = STATE_STOPPED;
mSensorOverlays.hide(getSensorId());
} else {
mState = STATE_STARTED_PAUSED_ATTEMPTED;
long delay = 0;
if (authenticated && mSensorProps.isAnySidefpsType()) {
if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
return;
}
delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps waiting for power until: " + delay);
}
mHandler.postDelayed(
() -> {
if (authenticated && mSensorProps.isAnySidefpsType()) {
Slog.i(TAG, "(sideFPS): No power press detected, sending auth");
}
super.onAuthenticated(identifier, authenticated, token);
if (authenticated) {
mState = STATE_STOPPED;
mSensorOverlays.hide(getSensorId());
} else {
mState = STATE_STARTED_PAUSED_ATTEMPTED;
}
},
MESSAGE_AUTH_SUCCESS,
delay);
}
@Override
@@ -165,7 +248,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mCancellationSignal = doAuthenticate();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
onError(
BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
@@ -177,15 +261,18 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
if (session.hasContextMethods()) {
final OperationContext opContext = getOperationContext();
final ICancellationSignal cancel = session.getSession().authenticateWithContext(
mOperationId, opContext);
getBiometricContext().subscribe(opContext, ctx -> {
try {
session.getSession().onContextChanged(ctx);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify context changed", e);
}
});
final ICancellationSignal cancel =
session.getSession().authenticateWithContext(mOperationId, opContext);
getBiometricContext()
.subscribe(
opContext,
ctx -> {
try {
session.getSession().onContextChanged(ctx);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify context changed", e);
}
});
return cancel;
} else {
return session.getSession().authenticate(mOperationId);
@@ -202,7 +289,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mCancellationSignal.cancel();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
onError(
BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
}
@@ -284,8 +372,13 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
// Lockout metrics are logged as an error code.
final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
getLogger().logOnError(getContext(), getOperationContext(),
error, 0 /* vendorCode */, getTargetUserId());
getLogger()
.logOnError(
getContext(),
getOperationContext(),
error,
0 /* vendorCode */,
getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -303,8 +396,13 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
// Lockout metrics are logged as an error code.
final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
getLogger().logOnError(getContext(), getOperationContext(),
error, 0 /* vendorCode */, getTargetUserId());
getLogger()
.logOnError(
getContext(),
getOperationContext(),
error,
0 /* vendorCode */,
getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -315,4 +413,19 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
}
@Override
public void onPowerPressed() {
if (mSensorProps.isAnySidefpsType()) {
Slog.i(TAG, "(sideFPS): onPowerPressed");
if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
// Do not call onError() as that will send an additional callback to coex.
onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
}
mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
mHandler.postDelayed(() -> {}, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
}
}
}

View File

@@ -22,6 +22,7 @@ import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.PointerContext;
@@ -139,7 +140,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
controller.onEnrollmentHelp(getSensorId());
}
});
mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
super.onAcquired(acquiredInfo, vendorCode);
}

View File

@@ -67,6 +67,7 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
@@ -387,6 +388,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mBiometricStateCallback.onClientStarted(clientMonitor);
}
@Override
public void onBiometricAction(int action) {
mBiometricStateCallback.onBiometricAction(action);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
@@ -441,7 +447,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mBiometricContext, isStrongBiometric,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties());
mSensors.get(sensorId).getSensorProperties(), mHandler);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -613,6 +619,21 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mUdfpsOverlayController = controller;
}
@Override
public void onPowerPressed() {
for (int i = 0; i < mSensors.size(); i++) {
final Sensor sensor = mSensors.valueAt(i);
BaseClientMonitor client = sensor.getScheduler().getCurrentClient();
if (client == null) {
return;
}
if (!(client instanceof PowerPressHandler)) {
continue;
}
((PowerPressHandler) client).onPowerPressed();
}
}
@Override
public void setSidefpsController(@NonNull ISidefpsController controller) {
mSidefpsController = controller;

View File

@@ -602,6 +602,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mBiometricStateCallback.onClientStarted(clientMonitor);
}
@Override
public void onBiometricAction(int action) {
mBiometricStateCallback.onBiometricAction(action);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
@@ -820,6 +825,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
});
}
@Override
public void onPowerPressed() {
Slog.e(TAG, "onPowerPressed not supported for HIDL clients");
}
@Override
public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
mUdfpsOverlayController = controller;

View File

@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -151,6 +152,8 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
controller.onEnrollmentHelp(getSensorId());
}
});
mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
}
@Override

View File

@@ -949,6 +949,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) {
// SideFPS still needs to know about suppressed power buttons, in case it needs to block
// an auth attempt.
if (count == 1) {
mSideFpsEventHandler.notifyPowerPressed();
}
if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) {
Slog.i(TAG, "Suppressed redundant power key press while "
+ "already in the process of turning the screen on.");
@@ -968,7 +973,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
} else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
Slog.d(TAG, "No behavior defined for power press count " + count);
} else if (count == 1 && interactive && !beganFromNonInteractive) {
if (mSideFpsEventHandler.onSinglePressDetected(eventTime)) {
if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) {
Slog.i(TAG, "Suppressing power key because the user is interacting with the "
+ "fingerprint sensor");
return;

View File

@@ -19,6 +19,7 @@ package com.android.server.policy;
import static android.hardware.biometrics.BiometricStateListener.STATE_BP_AUTH;
import static android.hardware.biometrics.BiometricStateListener.STATE_ENROLLING;
import static android.hardware.biometrics.BiometricStateListener.STATE_IDLE;
import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,8 +35,12 @@ import android.hardware.biometrics.BiometricStateListener;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.view.WindowManager;
import com.android.internal.R;
@@ -46,14 +51,26 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
/**
* Defines behavior for handling interactions between power button events and
* fingerprint-related operations, for devices where the fingerprint sensor (side fps)
* lives on the power button.
* Defines behavior for handling interactions between power button events and fingerprint-related
* operations, for devices where the fingerprint sensor (side fps) lives on the power button.
*/
public class SideFpsEventHandler {
private static final int DEBOUNCE_DELAY_MILLIS = 500;
private int getTapWaitForPowerDuration(Context context) {
int tap = context.getResources().getInteger(
R.integer.config_sidefpsEnrollPowerPressWindow);
if (Build.isDebuggable()) {
tap = Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, tap,
UserHandle.USER_CURRENT);
}
return tap;
}
private static final String TAG = "SideFpsEventHandler";
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final PowerManager mPowerManager;
@@ -61,20 +78,26 @@ public class SideFpsEventHandler {
@NonNull private final AtomicBoolean mSideFpsEventHandlerReady;
@Nullable private Dialog mDialog;
@NonNull private final DialogInterface.OnDismissListener mDialogDismissListener = (dialog) -> {
if (mDialog == dialog) {
mDialog = null;
}
};
private final Runnable mTurnOffDialog =
() -> {
dismissDialog("mTurnOffDialog");
};
@NonNull private final DialogInterface.OnDismissListener mDialogDismissListener;
private @BiometricStateListener.State int mBiometricState;
private final int mTapWaitForPowerDuration;
private FingerprintManager mFingerprintManager;
SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) {
this(context, handler, powerManager, () -> new AlertDialog.Builder(context));
}
@VisibleForTesting
SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager,
SideFpsEventHandler(
Context context,
Handler handler,
PowerManager powerManager,
Supplier<AlertDialog.Builder> dialogSupplier) {
mContext = context;
mHandler = handler;
@@ -82,91 +105,131 @@ public class SideFpsEventHandler {
mDialogSupplier = dialogSupplier;
mBiometricState = STATE_IDLE;
mSideFpsEventHandlerReady = new AtomicBoolean(false);
mDialogDismissListener =
(dialog) -> {
if (mDialog == dialog) {
if (mHandler != null) {
mHandler.removeCallbacks(mTurnOffDialog);
}
mDialog = null;
}
};
// ensure dialog is dismissed if screen goes off for unrelated reasons
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
}
}, new IntentFilter(Intent.ACTION_SCREEN_OFF));
context.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
}
},
new IntentFilter(Intent.ACTION_SCREEN_OFF));
mTapWaitForPowerDuration = getTapWaitForPowerDuration(context);
}
/**
* Called from {@link PhoneWindowManager} after the power button is pressed and displays a
* dialog confirming the user's intent to turn screen off if a fingerprint operation is
* active. The device goes to sleep if confirmed otherwise the dialog is dismissed.
* Called from {@link PhoneWindowManager} to notify FingerprintManager that a single tap power
* button has been pressed.
*/
public void notifyPowerPressed() {
Log.i(TAG, "notifyPowerPressed");
if (mFingerprintManager == null) {
mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
}
if (mFingerprintManager == null) {
return;
}
mFingerprintManager.onPowerPressed();
}
/**
* Called from {@link PhoneWindowManager} and will dictate if the SideFpsEventHandler should
* handle the power press.
*
* @param eventTime powerPress event time
* @return true if powerPress was consumed, false otherwise
*/
public boolean onSinglePressDetected(long eventTime) {
public boolean shouldConsumeSinglePress(long eventTime) {
if (!mSideFpsEventHandlerReady.get()) {
return false;
}
switch (mBiometricState) {
case STATE_ENROLLING:
case STATE_BP_AUTH:
mHandler.post(() -> {
if (mDialog != null) {
mDialog.dismiss();
}
mDialog = showConfirmDialog(mDialogSupplier.get(),
mPowerManager, eventTime, mBiometricState, mDialogDismissListener);
});
mHandler.post(
() -> {
if (mHandler.hasCallbacks(mTurnOffDialog)) {
Log.v(TAG, "Detected a tap to turn off dialog, ignoring");
mHandler.removeCallbacks(mTurnOffDialog);
}
});
showDialog(eventTime, "Enroll Power Press");
return true;
case STATE_BP_AUTH:
return true;
case STATE_KEYGUARD_AUTH:
default:
return false;
}
}
@NonNull
private static Dialog showConfirmDialog(@NonNull AlertDialog.Builder dialogBuilder,
@NonNull PowerManager powerManager, long eventTime,
private static Dialog showConfirmDialog(
@NonNull AlertDialog.Builder dialogBuilder,
@NonNull PowerManager powerManager,
long eventTime,
@BiometricStateListener.State int biometricState,
@NonNull DialogInterface.OnDismissListener dismissListener) {
final boolean enrolling = biometricState == STATE_ENROLLING;
final int title = enrolling ? R.string.fp_power_button_enrollment_title
: R.string.fp_power_button_bp_title;
final int message = enrolling ? R.string.fp_power_button_enrollment_message
: R.string.fp_power_button_bp_message;
final int positiveText = enrolling ? R.string.fp_power_button_enrollment_positive_button
: R.string.fp_power_button_bp_positive_button;
final int negativeText = enrolling ? R.string.fp_power_button_enrollment_negative_button
: R.string.fp_power_button_bp_negative_button;
final int title =
enrolling
? R.string.fp_power_button_enrollment_title
: R.string.fp_power_button_bp_title;
final int message =
enrolling
? R.string.fp_power_button_enrollment_message
: R.string.fp_power_button_bp_message;
final int positiveText =
enrolling
? R.string.fp_power_button_enrollment_positive_button
: R.string.fp_power_button_bp_positive_button;
final int negativeText =
enrolling
? R.string.fp_power_button_enrollment_negative_button
: R.string.fp_power_button_bp_negative_button;
final Dialog confirmScreenOffDialog = dialogBuilder
.setTitle(title)
.setMessage(message)
.setPositiveButton(positiveText,
(dialog, which) -> {
dialog.dismiss();
powerManager.goToSleep(
eventTime,
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
0 /* flags */
);
})
.setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
.setOnDismissListener(dismissListener)
.setCancelable(false)
.create();
confirmScreenOffDialog.getWindow().setType(
WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
final Dialog confirmScreenOffDialog =
dialogBuilder
.setTitle(title)
.setMessage(message)
.setPositiveButton(
positiveText,
(dialog, which) -> {
dialog.dismiss();
powerManager.goToSleep(
eventTime,
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
0 /* flags */);
})
.setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
.setOnDismissListener(dismissListener)
.setCancelable(false)
.create();
confirmScreenOffDialog
.getWindow()
.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
confirmScreenOffDialog.show();
return confirmScreenOffDialog;
}
/**
* Awaits notification from PhoneWindowManager that fingerprint service is ready
* to send updates about power button fps sensor state. Then configures a
* BiometricStateListener to receive and record updates to fps state, and
* registers the BiometricStateListener in FingerprintManager.
* Awaits notification from PhoneWindowManager that fingerprint service is ready to send updates
* about power button fps sensor state. Then configures a BiometricStateListener to receive and
* record updates to fps state, and registers the BiometricStateListener in FingerprintManager.
*/
public void onFingerprintSensorReady() {
final PackageManager pm = mContext.getPackageManager();
@@ -184,12 +247,12 @@ public class SideFpsEventHandler {
if (fingerprintManager.isPowerbuttonFps()) {
fingerprintManager.registerBiometricStateListener(
new BiometricStateListener() {
@Nullable
private Runnable mStateRunnable = null;
@Nullable private Runnable mStateRunnable = null;
@Override
public void onStateChanged(
@BiometricStateListener.State int newState) {
Log.d(TAG, "onStateChanged : " + newState);
if (mStateRunnable != null) {
mHandler.removeCallbacks(mStateRunnable);
mStateRunnable = null;
@@ -200,16 +263,58 @@ public class SideFpsEventHandler {
// damper when moving to idle in case auth is first
if (newState == STATE_IDLE) {
mStateRunnable = () -> mBiometricState = newState;
mHandler.postDelayed(mStateRunnable,
DEBOUNCE_DELAY_MILLIS);
// This is also useful in the case of biometric
// prompt.
// If a user has recently succeeded/failed auth, we
// want to disable the power button for a short
// period of time (so ethey are able to view the
// prompt)
mHandler.postDelayed(
mStateRunnable, DEBOUNCE_DELAY_MILLIS);
dismissDialog("STATE_IDLE");
} else {
mBiometricState = newState;
}
}
@Override
public void onBiometricAction(
@BiometricStateListener.Action int action) {
Log.d(TAG, "onBiometricAction " + action);
switch (action) {
case BiometricStateListener.ACTION_SENSOR_TOUCH:
mHandler.postDelayed(
mTurnOffDialog,
mTapWaitForPowerDuration);
break;
}
}
});
mSideFpsEventHandlerReady.set(true);
}
}
});
}
private void dismissDialog(String reason) {
Log.d(TAG, "Dismissing dialog with reason: " + reason);
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
}
private void showDialog(long time, String reason) {
Log.d(TAG, "Showing dialog with reason: " + reason);
if (mDialog != null && mDialog.isShowing()) {
Log.d(TAG, "Ignoring show dialog");
return;
}
mDialog =
showConfirmDialog(
mDialogSupplier.get(),
mPowerManager,
time,
mBiometricState,
mDialogDismissListener);
}
}

View File

@@ -43,8 +43,10 @@ import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
@@ -128,6 +130,8 @@ public class FingerprintAuthenticationClientTest {
@Captor
private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
private TestLooper mLooper = new TestLooper();
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -233,6 +237,9 @@ public class FingerprintAuthenticationClientTest {
client.start(mCallback);
client.onAuthenticated(new Fingerprint("name", 2 /* enrollmentId */, SENSOR_ID),
true /* authenticated */, new ArrayList<>());
mLooper.moveTimeForward(10);
mLooper.dispatchAll();
verify(mLuxProbe).destroy();
client.onAcquired(2, 0);
@@ -309,9 +316,58 @@ public class FingerprintAuthenticationClientTest {
client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */),
true /* authenticated */, new ArrayList<>());
mLooper.moveTimeForward(10);
mLooper.dispatchAll();
verify(mCancellationSignal).cancel();
}
@Test
public void fingerprintPowerIgnoresAuthInWindow() throws Exception {
when(mSensorProps.isAnySidefpsType()).thenReturn(true);
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
client.onPowerPressed();
client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */),
true /* authenticated */, new ArrayList<>());
mLooper.moveTimeForward(1000);
mLooper.dispatchAll();
verify(mCallback).onClientFinished(any(), eq(false));
}
@Test
public void fingerprintAuthIgnoredWaitingForPower() throws Exception {
when(mSensorProps.isAnySidefpsType()).thenReturn(true);
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
client.onAuthenticated(new Fingerprint("friendly", 3 /* fingerId */, 4 /* deviceId */),
true /* authenticated */, new ArrayList<>());
client.onPowerPressed();
mLooper.moveTimeForward(1000);
mLooper.dispatchAll();
verify(mCallback).onClientFinished(any(), eq(false));
}
@Test
public void fingerprintAuthSucceedsAfterPowerWindow() throws Exception {
when(mSensorProps.isAnySidefpsType()).thenReturn(true);
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
client.onPowerPressed();
mLooper.moveTimeForward(1000);
mLooper.dispatchAll();
client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
true /* authenticated */, new ArrayList<>());
mLooper.moveTimeForward(1000);
mLooper.dispatchAll();
verify(mCallback).onClientFinished(any(), eq(true));
}
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
@@ -336,7 +392,8 @@ public class FingerprintAuthenticationClientTest {
9 /* sensorId */, mBiometricLogger, mBiometricContext,
true /* isStrongBiometric */,
null /* taskStackListener */, mLockoutCache,
mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps) {
mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps,
new Handler(mLooper.getLooper())) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;

View File

@@ -53,33 +53,30 @@ import java.util.List;
/**
* Unit tests for {@link SideFpsEventHandler}.
* <p/>
* Run with <code>atest SideFpsEventHandlerTest</code>.
*
* <p>Run with <code>atest SideFpsEventHandlerTest</code>.
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class SideFpsEventHandlerTest {
private static final List<Integer> sAllStates = List.of(
BiometricStateListener.STATE_IDLE,
BiometricStateListener.STATE_ENROLLING,
BiometricStateListener.STATE_KEYGUARD_AUTH,
BiometricStateListener.STATE_BP_AUTH,
BiometricStateListener.STATE_AUTH_OTHER);
private static final List<Integer> sAllStates =
List.of(
BiometricStateListener.STATE_IDLE,
BiometricStateListener.STATE_ENROLLING,
BiometricStateListener.STATE_KEYGUARD_AUTH,
BiometricStateListener.STATE_BP_AUTH,
BiometricStateListener.STATE_AUTH_OTHER);
@Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
@Mock
private PackageManager mPackageManager;
@Mock
private FingerprintManager mFingerprintManager;
@Spy
private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
@Mock
private AlertDialog mAlertDialog;
@Mock
private Window mWindow;
@Mock private PackageManager mPackageManager;
@Mock private FingerprintManager mFingerprintManager;
@Spy private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
@Mock private AlertDialog mAlertDialog;
@Mock private Window mWindow;
private TestLooper mLooper = new TestLooper();
private SideFpsEventHandler mEventHandler;
@@ -95,9 +92,12 @@ public class SideFpsEventHandlerTest {
when(mDialogBuilder.create()).thenReturn(mAlertDialog);
when(mAlertDialog.getWindow()).thenReturn(mWindow);
mEventHandler = new SideFpsEventHandler(
mContext, new Handler(mLooper.getLooper()),
mContext.getSystemService(PowerManager.class), () -> mDialogBuilder);
mEventHandler =
new SideFpsEventHandler(
mContext,
new Handler(mLooper.getLooper()),
mContext.getSystemService(PowerManager.class),
() -> mDialogBuilder);
}
@Test
@@ -105,7 +105,7 @@ public class SideFpsEventHandlerTest {
when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
.thenReturn(false);
assertThat(mEventHandler.onSinglePressDetected(60L)).isFalse();
assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -117,7 +117,7 @@ public class SideFpsEventHandlerTest {
for (int state : sAllStates) {
setBiometricState(state);
assertThat(mEventHandler.onSinglePressDetected(200L)).isFalse();
assertThat(mEventHandler.shouldConsumeSinglePress(200L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -130,7 +130,7 @@ public class SideFpsEventHandlerTest {
for (int state : sAllStates) {
setBiometricState(state);
assertThat(mEventHandler.onSinglePressDetected(400L)).isFalse();
assertThat(mEventHandler.shouldConsumeSinglePress(400L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -139,13 +139,13 @@ public class SideFpsEventHandlerTest {
@Test
public void ignoresWhenIdleOrUnknown() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setupWithSensor(true /* hasSidefps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_IDLE);
assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse();
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
setBiometricState(BiometricStateListener.STATE_AUTH_OTHER);
assertThat(mEventHandler.onSinglePressDetected(90000L)).isFalse();
assertThat(mEventHandler.shouldConsumeSinglePress(90000L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -156,7 +156,7 @@ public class SideFpsEventHandlerTest {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_KEYGUARD_AUTH);
assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse();
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -164,13 +164,13 @@ public class SideFpsEventHandlerTest {
@Test
public void promptsWhenBPisActive() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setupWithSensor(true /* hasSideFps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_BP_AUTH);
assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue();
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
verify(mAlertDialog).show();
verify(mAlertDialog, never()).show();
}
@Test
@@ -178,7 +178,57 @@ public class SideFpsEventHandlerTest {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue();
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
verify(mAlertDialog).show();
verify(mAlertDialog, never()).dismiss();
}
@Test
public void dismissesDialogOnTouchWhenEnrolling() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
when(mAlertDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
verify(mAlertDialog).show();
mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
mLooper.moveTimeForward(10000);
mLooper.dispatchAll();
verify(mAlertDialog).dismiss();
}
@Test
public void dismissesDialogFailsWhenPowerPressedAndDialogShowing() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
when(mAlertDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
verify(mAlertDialog).show();
mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
mLooper.dispatchAll();
verify(mAlertDialog, never()).dismiss();
}
@Test
public void showDialogAfterTap() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
when(mAlertDialog.isShowing()).thenReturn(true);
mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
mLooper.dispatchAll();
verify(mAlertDialog).show();
@@ -201,11 +251,13 @@ public class SideFpsEventHandlerTest {
ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(fpCallbackCaptor.capture());
if (initialized) {
fpCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
List.of(mock(FingerprintSensorPropertiesInternal.class)));
fpCallbackCaptor
.getValue()
.onAllAuthenticatorsRegistered(
List.of(mock(FingerprintSensorPropertiesInternal.class)));
if (hasSfps) {
ArgumentCaptor<BiometricStateListener> captor = ArgumentCaptor.forClass(
BiometricStateListener.class);
ArgumentCaptor<BiometricStateListener> captor =
ArgumentCaptor.forClass(BiometricStateListener.class);
verify(mFingerprintManager).registerBiometricStateListener(captor.capture());
mBiometricStateListener = captor.getValue();
}