Merge changes from topic "sunfish-udfps"

* changes:
  Add anti burn-in logic for UdfpsView
  Update Fingerprint21, add Fingerprint21UdfpsMock
This commit is contained in:
Kevin Chyn
2020-08-16 01:25:00 +00:00
committed by Android (Google) Code Review
20 changed files with 846 additions and 70 deletions

View File

@@ -25,4 +25,7 @@ oneway interface IUdfpsOverlayController {
// Hides the overlay.
void hideUdfpsOverlay();
// Shows debug messages on the UDFPS overlay.
void setDebugMessage(String message);
}

View File

@@ -4189,6 +4189,8 @@
<string-array name="config_biometric_sensors" translatable="false" >
<!-- <item>0:2:15</item> ID0:Fingerprint:Strong -->
</string-array>
<!--If true, allows the device to load udfps components on older HIDL implementations -->
<bool name="allow_test_udfps" translatable="false" >false</bool>
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
used to hide messages that may be too chatty or messages that the user can't do much about.

View File

@@ -2506,6 +2506,7 @@
<java-symbol type="string" name="face_error_security_update_required" />
<java-symbol type="array" name="config_biometric_sensors" />
<java-symbol type="bool" name="allow_test_udfps" />
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />

View File

@@ -1036,6 +1036,11 @@
<!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. -->
<dimen name="default_burn_in_prevention_offset">15dp</dimen>
<!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either
direction that elements aer moved to prevent burn-in on AOD-->
<dimen name="udfps_burn_in_offset_x">8dp</dimen>
<dimen name="udfps_burn_in_offset_y">8dp</dimen>
<dimen name="corner_size">8dp</dimen>
<dimen name="top_padding">0dp</dimen>
<dimen name="bottom_padding">48dp</dimen>

View File

@@ -49,6 +49,8 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.systemui.SystemUI;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import java.util.List;
@@ -62,12 +64,13 @@ import javax.inject.Singleton;
*/
@Singleton
public class AuthController extends SystemUI implements CommandQueue.Callbacks,
AuthDialogCallback {
AuthDialogCallback, DozeReceiver {
private static final String TAG = "BiometricPrompt/AuthController";
private static final String TAG = "AuthController";
private static final boolean DEBUG = true;
private final CommandQueue mCommandQueue;
private final StatusBarStateController mStatusBarStateController;
private final Injector mInjector;
// TODO: These should just be saved from onSaveState
@@ -77,6 +80,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
private Handler mHandler = new Handler(Looper.getMainLooper());
private WindowManager mWindowManager;
@Nullable
private UdfpsController mUdfpsController;
@VisibleForTesting
IActivityTaskManager mActivityTaskManager;
@@ -142,6 +146,13 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
};
@Override
public void dozeTimeTick() {
if (mUdfpsController != null) {
mUdfpsController.dozeTimeTick();
}
}
@Override
public void onTryAgainPressed() {
if (mReceiver == null) {
@@ -251,14 +262,17 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
@Inject
public AuthController(Context context, CommandQueue commandQueue) {
this(context, commandQueue, new Injector());
public AuthController(Context context, CommandQueue commandQueue,
StatusBarStateController statusBarStateController) {
this(context, commandQueue, statusBarStateController, new Injector());
}
@VisibleForTesting
AuthController(Context context, CommandQueue commandQueue, Injector injector) {
AuthController(Context context, CommandQueue commandQueue,
StatusBarStateController statusBarStateController, Injector injector) {
super(context);
mCommandQueue = commandQueue;
mStatusBarStateController = statusBarStateController;
mInjector = injector;
IntentFilter filter = new IntentFilter();
@@ -280,7 +294,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
fpm.getSensorProperties();
for (FingerprintSensorProperties props : fingerprintSensorProperties) {
if (props.sensorType == FingerprintSensorProperties.TYPE_UDFPS) {
mUdfpsController = new UdfpsController(mContext);
mUdfpsController = new UdfpsController(mContext, mStatusBarStateController);
break;
}
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.biometrics;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
@@ -29,6 +30,7 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.Spline;
@@ -38,6 +40,8 @@ import android.view.WindowManager;
import com.android.internal.BrightnessSynchronizer;
import com.android.systemui.R;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import java.io.FileWriter;
import java.io.IOException;
@@ -46,7 +50,7 @@ import java.io.IOException;
* Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
* and coordinates triggering of the high-brightness mode (HBM).
*/
class UdfpsController {
class UdfpsController implements DozeReceiver {
private static final String TAG = "UdfpsController";
// Gamma approximation for the sRGB color space.
private static final float DISPLAY_GAMMA = 2.2f;
@@ -61,6 +65,7 @@ class UdfpsController {
private final String mHbmPath;
private final String mHbmEnableCommand;
private final String mHbmDisableCommand;
private final boolean mHbmSupported;
// Brightness in nits in the high-brightness mode.
private final float mHbmNits;
// A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to a
@@ -85,6 +90,11 @@ class UdfpsController {
public void hideUdfpsOverlay() {
UdfpsController.this.hideUdfpsOverlay();
}
@Override
public void setDebugMessage(String message) {
mView.setDebugMessage(message);
}
}
@SuppressLint("ClickableViewAccessibility")
@@ -116,7 +126,8 @@ class UdfpsController {
}
};
UdfpsController(Context context) {
UdfpsController(@NonNull Context context,
@NonNull StatusBarStateController statusBarStateController) {
mFingerprintManager = context.getSystemService(FingerprintManager.class);
mWindowManager = context.getSystemService(WindowManager.class);
mContentResolver = context.getContentResolver();
@@ -129,6 +140,10 @@ class UdfpsController {
mHbmEnableCommand = context.getResources().getString(R.string.udfps_hbm_enable_command);
mHbmDisableCommand = context.getResources().getString(R.string.udfps_hbm_disable_command);
mHbmSupported = !TextUtils.isEmpty(mHbmPath);
mView.setHbmSupported(mHbmSupported);
statusBarStateController.addCallback(mView);
// This range only consists of the minimum and maximum values, which only cover
// non-high-brightness mode.
float[] nitsRange = toFloatArray(context.getResources().obtainTypedArray(
@@ -162,6 +177,11 @@ class UdfpsController {
mIsOverlayShowing = false;
}
@Override
public void dozeTimeTick() {
mView.dozeTimeTick();
}
private void showUdfpsOverlay() {
mHandler.post(() -> {
if (!mIsOverlayShowing) {
@@ -224,9 +244,11 @@ class UdfpsController {
mView.setScrimAlpha(computeScrimOpacity());
mView.showScrimAndDot();
try {
FileWriter fw = new FileWriter(mHbmPath);
fw.write(mHbmEnableCommand);
fw.close();
if (mHbmSupported) {
FileWriter fw = new FileWriter(mHbmPath);
fw.write(mHbmEnableCommand);
fw.close();
}
mFingerprintManager.onFingerDown(x, y, minor, major);
} catch (IOException e) {
mView.hideScrimAndDot();
@@ -238,13 +260,15 @@ class UdfpsController {
mFingerprintManager.onFingerUp();
// Hiding the scrim before disabling HBM results in less noticeable flicker.
mView.hideScrimAndDot();
try {
FileWriter fw = new FileWriter(mHbmPath);
fw.write(mHbmDisableCommand);
fw.close();
} catch (IOException e) {
mView.showScrimAndDot();
Log.e(TAG, "onFingerUp | failed to disable HBM: " + e.getMessage());
if (mHbmSupported) {
try {
FileWriter fw = new FileWriter(mHbmPath);
fw.write(mHbmDisableCommand);
fw.close();
} catch (IOException e) {
mView.showScrimAndDot();
Log.e(TAG, "onFingerUp | failed to disable HBM: " + e.getMessage());
}
}
}

View File

@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -23,25 +25,33 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewTreeObserver;
import com.android.systemui.R;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
/**
* A full screen view with a configurable illumination dot and scrim.
*/
public class UdfpsView extends View {
public class UdfpsView extends View implements DozeReceiver,
StatusBarStateController.StateListener {
private static final String TAG = "UdfpsView";
// Values in pixels.
private static final float SENSOR_SHADOW_OFFSET = 2.0f;
private static final float SENSOR_SHADOW_RADIUS = 2.0f;
private static final float SENSOR_OUTLINE_WIDTH = 2.0f;
private static final int DEBUG_TEXT_SIZE_PX = 32;
private final Rect mScrimRect;
private final Paint mScrimPaint;
private final Paint mDebugTextPaint;
private float mSensorX;
private float mSensorY;
@@ -50,11 +60,20 @@ public class UdfpsView extends View {
private final float mSensorRadius;
private final float mSensorMarginBottom;
private final float mSensorTouchAreaCoefficient;
private final int mMaxBurnInOffsetX;
private final int mMaxBurnInOffsetY;
private final Rect mTouchableRegion;
private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener;
// AOD anti-burn-in offsets
private float mInterpolatedDarkAmount;
private float mBurnInOffsetX;
private float mBurnInOffsetY;
private boolean mIsScrimShowing;
private boolean mHbmSupported;
private String mDebugMessage;
public UdfpsView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -80,18 +99,29 @@ public class UdfpsView extends View {
a.recycle();
}
mMaxBurnInOffsetX = getResources()
.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
mMaxBurnInOffsetY = getResources()
.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
mScrimRect = new Rect();
mScrimPaint = new Paint(0 /* flags */);
mScrimPaint.setColor(Color.BLACK);
mSensorRect = new RectF();
mSensorPaint = new Paint(0 /* flags */);
mSensorPaint.setAntiAlias(true);
mSensorPaint.setColor(Color.WHITE);
mSensorPaint.setStyle(Paint.Style.STROKE);
mSensorPaint.setStrokeWidth(SENSOR_OUTLINE_WIDTH);
mSensorPaint.setShadowLayer(SENSOR_OUTLINE_WIDTH, 0, 0, Color.BLACK);
mSensorPaint.setShadowLayer(SENSOR_SHADOW_RADIUS, 0, 0, Color.BLACK);
mSensorPaint.setAntiAlias(true);
mDebugTextPaint = new Paint();
mDebugTextPaint.setAntiAlias(true);
mDebugTextPaint.setColor(Color.BLUE);
mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX);
mTouchableRegion = new Rect();
mInsetsListener = internalInsetsInfo -> {
internalInsetsInfo.setTouchableInsets(
@@ -102,6 +132,29 @@ public class UdfpsView extends View {
mIsScrimShowing = false;
}
@Override
public void dozeTimeTick() {
updateAodPosition();
}
@Override
public void onDozeAmountChanged(float linear, float eased) {
mInterpolatedDarkAmount = eased;
updateAodPosition();
}
private void updateAodPosition() {
mBurnInOffsetX = MathUtils.lerp(0f,
getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
- mMaxBurnInOffsetX,
mInterpolatedDarkAmount);
mBurnInOffsetY = MathUtils.lerp(0f,
getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
- 0.5f * mMaxBurnInOffsetY,
mInterpolatedDarkAmount);
postInvalidate();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -131,10 +184,29 @@ public class UdfpsView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mIsScrimShowing) {
if (mIsScrimShowing && mHbmSupported) {
// Only draw the scrim if HBM is supported.
canvas.drawRect(mScrimRect, mScrimPaint);
}
// Translation should affect everything but the scrim.
canvas.save();
canvas.translate(mBurnInOffsetX, mBurnInOffsetY);
if (!TextUtils.isEmpty(mDebugMessage)) {
canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint);
}
canvas.drawOval(mSensorRect, mSensorPaint);
canvas.restore();
}
void setHbmSupported(boolean hbmSupported) {
mHbmSupported = hbmSupported;
}
void setDebugMessage(String message) {
mDebugMessage = message;
postInvalidate();
}
boolean isValidTouch(float x, float y, float pressure) {

View File

@@ -31,6 +31,7 @@ import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
@@ -88,6 +89,7 @@ public final class DozeServiceHost implements DozeHost {
private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private final LockscreenLockIconController mLockscreenLockIconController;
private final AuthController mAuthController;
private NotificationIconAreaController mNotificationIconAreaController;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private NotificationPanelViewController mNotificationPanel;
@@ -110,7 +112,8 @@ public final class DozeServiceHost implements DozeHost {
PulseExpansionHandler pulseExpansionHandler,
NotificationShadeWindowController notificationShadeWindowController,
NotificationWakeUpCoordinator notificationWakeUpCoordinator,
LockscreenLockIconController lockscreenLockIconController) {
LockscreenLockIconController lockscreenLockIconController,
AuthController authController) {
super();
mDozeLog = dozeLog;
mPowerManager = powerManager;
@@ -130,6 +133,7 @@ public final class DozeServiceHost implements DozeHost {
mNotificationShadeWindowController = notificationShadeWindowController;
mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
mLockscreenLockIconController = lockscreenLockIconController;
mAuthController = authController;
}
// TODO: we should try to not pass status bar in here if we can avoid it.
@@ -297,6 +301,7 @@ public final class DozeServiceHost implements DozeHost {
@Override
public void dozeTimeTick() {
mNotificationPanel.dozeTimeTick();
mAuthController.dozeTimeTick();
if (mAmbientIndicationContainer instanceof DozeReceiver) {
((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
}

View File

@@ -54,6 +54,7 @@ import android.testing.TestableLooper.RunWithLooper;
import com.android.internal.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.Before;
@@ -103,8 +104,8 @@ public class AuthControllerTest extends SysuiTestCase {
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
mAuthController = new TestableAuthController(
context, mock(CommandQueue.class), new MockInjector());
mAuthController = new TestableAuthController(context, mock(CommandQueue.class),
mock(StatusBarStateController.class), new MockInjector());
mAuthController.start();
}
@@ -502,8 +503,9 @@ public class AuthControllerTest extends SysuiTestCase {
private int mBuildCount = 0;
private PromptInfo mLastBiometricPromptInfo;
TestableAuthController(Context context, CommandQueue commandQueue, Injector injector) {
super(context, commandQueue, injector);
TestableAuthController(Context context, CommandQueue commandQueue,
StatusBarStateController statusBarStateController, Injector injector) {
super(context, commandQueue, statusBarStateController, injector);
}
@Override

View File

@@ -36,6 +36,7 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -90,6 +91,7 @@ public class DozeServiceHostTest extends SysuiTestCase {
@Mock private View mAmbientIndicationContainer;
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private LockscreenLockIconController mLockscreenLockIconController;
@Mock private AuthController mAuthController;
@Before
public void setup() {
@@ -100,7 +102,7 @@ public class DozeServiceHostTest extends SysuiTestCase {
mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
mKeyguardUpdateMonitor, mVisualStabilityManager, mPulseExpansionHandler,
mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
mLockscreenLockIconController);
mLockscreenLockIconController, mAuthController);
mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController,
mStatusBarKeyguardViewManager, mNotificationShadeWindowViewController,

View File

@@ -45,7 +45,7 @@ public abstract class AcquisitionClient<T> extends ClientMonitor<T> implements I
private final PowerManager mPowerManager;
private final VibrationEffect mSuccessVibrationEffect;
private final VibrationEffect mErrorVibrationEffect;
private boolean mShouldSendErrorToClient;
private boolean mShouldSendErrorToClient = true;
private boolean mAlreadyCancelled;
/**
@@ -85,11 +85,11 @@ public abstract class AcquisitionClient<T> extends ClientMonitor<T> implements I
// case (success, failure, or error) is received from the HAL (e.g. versions of fingerprint
// that do not handle lockout under the HAL. In these cases, ensure that the framework only
// sends errors once per ClientMonitor.
if (!mShouldSendErrorToClient) {
if (mShouldSendErrorToClient) {
logOnError(getContext(), errorCode, vendorCode, getTargetUserId());
try {
if (getListener() != null) {
mShouldSendErrorToClient = true;
mShouldSendErrorToClient = false;
getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
}
} catch (RemoteException e) {

View File

@@ -99,6 +99,14 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
return getCookie() != 0;
}
public long getOperationId() {
return mOperationId;
}
public boolean isRestricted() {
return mIsRestricted;
}
@Override
protected boolean isCryptoOperation() {
return mOperationId != 0;

View File

@@ -175,7 +175,7 @@ public class BiometricScheduler {
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService;
@NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull protected final InternalCallback mInternalCallback;
@NonNull private final InternalCallback mInternalCallback;
@NonNull private final Queue<Operation> mPendingOperations;
@Nullable private Operation mCurrentOperation;
@NonNull private final ArrayDeque<CrashState> mCrashStates;
@@ -185,7 +185,15 @@ public class BiometricScheduler {
// starting the next client).
public class InternalCallback implements ClientMonitor.Callback {
@Override
public void onClientFinished(ClientMonitor<?> clientMonitor, boolean success) {
public void onClientStarted(@NonNull ClientMonitor<?> clientMonitor) {
Slog.d(getTag(), "[Started] " + clientMonitor);
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
}
}
@Override
public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor, boolean success) {
mHandler.post(() -> {
if (mCurrentOperation == null) {
Slog.e(getTag(), "[Finishing] " + clientMonitor
@@ -235,6 +243,14 @@ public class BiometricScheduler {
mCrashStates = new ArrayDeque<>();
}
/**
* @return A reference to the internal callback that should be invoked whenever the scheduler
* needs to (e.g. client started, client finished).
*/
@NonNull protected InternalCallback getInternalCallback() {
return mInternalCallback;
}
private String getTag() {
return BASE_TAG + "/" + mBiometricTag;
}
@@ -262,7 +278,7 @@ public class BiometricScheduler {
}
final Interruptable interruptable = (Interruptable) currentClient;
interruptable.cancelWithoutStarting(mInternalCallback);
interruptable.cancelWithoutStarting(getInternalCallback());
// Now we wait for the client to send its FinishCallback, which kicks off the next
// operation.
return;
@@ -280,10 +296,7 @@ public class BiometricScheduler {
final boolean shouldStartNow = currentClient.getCookie() == 0;
if (shouldStartNow) {
Slog.d(getTag(), "[Starting] " + mCurrentOperation);
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientStarted(currentClient);
}
currentClient.start(mInternalCallback);
currentClient.start(getInternalCallback());
mCurrentOperation.state = Operation.STATE_STARTED;
} else {
try {
@@ -326,11 +339,8 @@ public class BiometricScheduler {
}
Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientStarted(mCurrentOperation.clientMonitor);
}
mCurrentOperation.state = Operation.STATE_STARTED;
mCurrentOperation.clientMonitor.start(mInternalCallback);
mCurrentOperation.clientMonitor.start(getInternalCallback());
}
/**

View File

@@ -144,6 +144,7 @@ public abstract class ClientMonitor<T> extends LoggableMonitor implements IBinde
*/
public void start(@NonNull Callback callback) {
mCallback = callback;
mCallback.onClientStarted(this);
}
/**

View File

@@ -58,6 +58,10 @@ public abstract class LoggableMonitor {
mStatsClient = statsClient;
}
public int getStatsClient() {
return mStatsClient;
}
private boolean isAnyFieldUnknown() {
return mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN
|| mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN

View File

@@ -84,7 +84,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
private static final String TAG = "Fingerprint21";
private static final int ENROLL_TIMEOUT_SEC = 60;
private final Context mContext;
final Context mContext;
private final IActivityTaskManager mActivityTaskManager;
private final FingerprintSensorProperties mSensorProperties;
private final BiometricScheduler mScheduler;
@@ -96,6 +96,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
private final Map<Integer, Long> mAuthenticatorIds;
@Nullable private IBiometricsFingerprint mDaemon;
@NonNull private final HalResultController mHalResultController;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -146,15 +147,37 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
}
};
private final IBiometricsFingerprintClientCallback mDaemonCallback =
new IBiometricsFingerprintClientCallback.Stub() {
public static class HalResultController extends IBiometricsFingerprintClientCallback.Stub {
/**
* Interface to sends results to the HalResultController's owner.
*/
public interface Callback {
/**
* Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
*/
void onHardwareUnavailable();
}
@NonNull private final Context mContext;
@NonNull final Handler mHandler;
@NonNull final BiometricScheduler mScheduler;
@Nullable private Callback mCallback;
HalResultController(@NonNull Context context, @NonNull Handler handler,
@NonNull BiometricScheduler scheduler) {
mContext = context;
mHandler = handler;
mScheduler = scheduler;
}
public void setCallback(@Nullable Callback callback) {
mCallback = callback;
}
@Override
public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
mHandler.post(() -> {
final CharSequence name = FingerprintUtils.getInstance()
.getUniqueName(mContext, mCurrentUserId);
final Fingerprint fingerprint = new Fingerprint(name, groupId, fingerId, deviceId);
final ClientMonitor<?> client = mScheduler.getCurrentClient();
if (!(client instanceof FingerprintEnrollClient)) {
Slog.e(TAG, "onEnrollResult for non-enroll client: "
@@ -162,6 +185,11 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
return;
}
final int currentUserId = client.getTargetUserId();
final CharSequence name = FingerprintUtils.getInstance()
.getUniqueName(mContext, currentUserId);
final Fingerprint fingerprint = new Fingerprint(name, groupId, fingerId, deviceId);
final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
enrollClient.onEnrollResult(fingerprint, remaining);
});
@@ -224,8 +252,9 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
mDaemon = null;
mCurrentUserId = UserHandle.USER_NULL;
if (mCallback != null) {
mCallback.onHardwareUnavailable();
}
}
});
}
@@ -262,20 +291,27 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
enumerateConsumer.onEnumerationResult(fp, remaining);
});
}
};
}
Fingerprint21(@NonNull Context context, int sensorId,
Fingerprint21(@NonNull Context context, @NonNull BiometricScheduler scheduler,
@NonNull Handler handler, int sensorId,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@NonNull HalResultController controller) {
mContext = context;
mScheduler = scheduler;
mHandler = handler;
mActivityTaskManager = ActivityTaskManager.getService();
mHandler = new Handler(Looper.getMainLooper());
mTaskStackListener = new BiometricTaskStackListener();
mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>());
mLazyDaemon = Fingerprint21.this::getDaemon;
mLockoutResetDispatcher = lockoutResetDispatcher;
mLockoutTracker = new LockoutFrameworkImpl(context, mLockoutResetCallback);
mScheduler = new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
mHalResultController = controller;
mHalResultController.setCallback(() -> {
mDaemon = null;
mCurrentUserId = UserHandle.USER_NULL;
});
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
@@ -303,6 +339,17 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
mSensorProperties = new FingerprintSensorProperties(sensorId, sensorType);
}
static Fingerprint21 newInstance(@NonNull Context context, int sensorId,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
final Handler handler = new Handler(Looper.getMainLooper());
final BiometricScheduler scheduler =
new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
final HalResultController controller = new HalResultController(context, handler, scheduler);
return new Fingerprint21(context, scheduler, handler, sensorId, lockoutResetDispatcher,
controller);
}
@Override
public void serviceDied(long cookie) {
Slog.e(TAG, "HAL died");
@@ -355,7 +402,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
// successfully set.
long halId = 0;
try {
halId = mDaemon.setNotify(mDaemonCallback);
halId = mDaemon.setNotify(mHalResultController);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to set callback for fingerprint HAL", e);
mDaemon = null;
@@ -373,6 +420,9 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
return mDaemon;
}
@Nullable IUdfpsOverlayController getUdfpsOverlayController() {
return mUdfpsOverlayController;
}
@LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
return mLockoutTracker.getLockoutModeForUser(userId);
}
@@ -492,7 +542,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, int cookie,
@NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
@Nullable Surface surface, boolean restricted, int statsClient) {
boolean restricted, int statsClient, boolean isKeyguard) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -500,8 +550,8 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mLazyDaemon, token, listener, userId, operationId, restricted,
opPackageName, cookie, false /* requireConfirmation */,
mSensorProperties.sensorId, isStrongBiometric, surface, statsClient,
mTaskStackListener, mLockoutTracker, mUdfpsOverlayController);
mSensorProperties.sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mLockoutTracker, mUdfpsOverlayController, isKeyguard);
mScheduler.scheduleClientMonitor(client);
});
}

View File

@@ -0,0 +1,555 @@
/*
* 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;
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.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.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import java.util.ArrayList;
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 FingerprintSensorProperties 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 final TestableInternalCallback mInternalCallback;
@NonNull private Fingerprint21UdfpsMock mFingerprint21;
TestableBiometricScheduler(@NonNull String tag,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
super(tag, gestureAvailabilityDispatcher);
mInternalCallback = new TestableInternalCallback();
}
class TestableInternalCallback extends InternalCallback {
@Override
public void onClientStarted(ClientMonitor<?> clientMonitor) {
super.onClientStarted(clientMonitor);
Slog.d(TAG, "Client started: " + clientMonitor);
mFingerprint21.setDebugMessage("Started: " + clientMonitor);
}
@Override
public void onClientFinished(ClientMonitor<?> clientMonitor, boolean success) {
super.onClientFinished(clientMonitor, success);
Slog.d(TAG, "Client finished: " + clientMonitor);
mFingerprint21.setDebugMessage("Finished: " + clientMonitor);
}
}
void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
mFingerprint21 = fingerprint21;
}
/**
* Expose the internal finish callback so it can be used for testing
*/
@Override
@NonNull protected InternalCallback getInternalCallback() {
return mInternalCallback;
}
}
/**
* All of the mocking/testing should happen in here. This way we don't need to modify the
* {@link com.android.server.biometrics.sensors.ClientMonitor} 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(@NonNull Context context, @NonNull Handler handler,
@NonNull BiometricScheduler scheduler) {
super(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 ClientMonitor<?> 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);
}
}
static Fingerprint21UdfpsMock newInstance(@NonNull Context context, int sensorId,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
Slog.d(TAG, "Creating Fingerprint23Mock!");
final Handler handler = new Handler(Looper.getMainLooper());
final TestableBiometricScheduler scheduler =
new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
final MockHalResultController controller =
new MockHalResultController(context, handler, scheduler);
return new Fingerprint21UdfpsMock(context, scheduler, handler, sensorId,
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 ClientMonitor<?> 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(token, operationId, user, cookie,
listener, opPackageName, restricted, statsClient, isKeyguard);
}
}
private Fingerprint21UdfpsMock(@NonNull Context context,
@NonNull TestableBiometricScheduler scheduler,
@NonNull Handler handler, int sensorId,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull MockHalResultController controller) {
super(context, scheduler, handler, sensorId, lockoutResetDispatcher, controller);
mScheduler = scheduler;
mScheduler.init(this);
mHandler = handler;
mSensorProperties = new FingerprintSensorProperties(sensorId,
FingerprintSensorProperties.TYPE_UDFPS);
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
FingerprintSensorProperties getFingerprintSensorProperties() {
return mSensorProperties;
}
@Override
void onFingerDown(int x, int y, float minor, float major) {
mHandler.post(() -> {
Slog.d(TAG, "onFingerDown");
final AuthenticationConsumer lastAuthenticatedConsumer =
mMockHalResultController.getLastAuthenticatedClient();
final ClientMonitor<?> 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
void onFingerUp() {
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(message);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when sending message: " + message, e);
}
}
}

View File

@@ -29,7 +29,6 @@ import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.view.Surface;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -47,6 +46,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
private static final String TAG = "Biometrics/FingerprintAuthClient";
private final boolean mIsKeyguard;
private final LockoutFrameworkImpl mLockoutFrameworkImpl;
@Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
@@ -54,16 +54,17 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
@NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
int sensorId, boolean isStrongBiometric, @Nullable Surface surface, int statsClient,
int sensorId, boolean isStrongBiometric, int statsClient,
@NonNull TaskStackListener taskStackListener,
@NonNull LockoutFrameworkImpl lockoutTracker,
@Nullable IUdfpsOverlayController udfpsOverlayController) {
@Nullable IUdfpsOverlayController udfpsOverlayController, boolean isKeyguard) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
lockoutTracker);
mLockoutFrameworkImpl = lockoutTracker;
mUdfpsOverlayController = udfpsOverlayController;
mIsKeyguard = isKeyguard;
}
@Override
@@ -145,4 +146,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
public void onFingerUp() {
UdfpsHelper.onFingerUp(getFreshDaemon());
}
public boolean isKeyguard() {
return mIsKeyguard;
}
}

View File

@@ -38,14 +38,17 @@ import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.NativeHandle;
import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
import android.view.Surface;
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
@@ -158,8 +161,8 @@ public class FingerprintService extends SystemService {
final int statsClient = isKeyguard ? BiometricsProtoEnums.CLIENT_KEYGUARD
: BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER;
mFingerprint21.scheduleAuthenticate(token, operationId, userId, 0 /* cookie */,
new ClientMonitorCallbackConverter(receiver), opPackageName, surface,
restricted, statsClient);
new ClientMonitorCallbackConverter(receiver), opPackageName,
restricted, statsClient, isKeyguard);
}
@Override
@@ -193,8 +196,8 @@ public class FingerprintService extends SystemService {
final boolean restricted = true; // BiometricPrompt is always restricted
mFingerprint21.scheduleAuthenticate(token, operationId, userId, cookie,
new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, surface,
restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT);
new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, false /* isKeyguard */);
}
@Override // Binder call
@@ -369,8 +372,18 @@ public class FingerprintService extends SystemService {
@Override // Binder call
public void initializeConfiguration(int sensorId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
mFingerprint21 = new Fingerprint21(getContext(), sensorId, mLockoutResetDispatcher,
mGestureAvailabilityDispatcher);
if ((Build.IS_USERDEBUG || Build.IS_ENG)
&& getContext().getResources().getBoolean(R.bool.allow_test_udfps)
&& Settings.Secure.getIntForUser(getContext().getContentResolver(),
Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
UserHandle.USER_CURRENT) != 0) {
mFingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), sensorId,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
} else {
mFingerprint21 = Fingerprint21.newInstance(getContext(), sensorId,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
}
@Override

View File

@@ -1156,7 +1156,7 @@ public class TrustManagerService extends SystemService {
}
private void enforceListenerPermission() {
mContext.enforceCallingPermission(Manifest.permission.TRUST_LISTENER,
mContext.enforceCallingOrSelfPermission(Manifest.permission.TRUST_LISTENER,
"register trust listener");
}