Merge changes from topic "biometric-refactor"

* changes:
  3/n: For passive modalities, add plumbing for "try again"
  2/n: Multi-modal support for BiometricPrompt
  1/n: Move BiometricDialog management to BiometricService
This commit is contained in:
Kevin Chyn
2018-12-01 05:04:02 +00:00
committed by Android (Google) Code Review
29 changed files with 1109 additions and 689 deletions

View File

@@ -158,9 +158,9 @@ java_defaults {
"core/java/android/hardware/IConsumerIrService.aidl",
"core/java/android/hardware/ISerialManager.aidl",
"core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl",
"core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl",
"core/java/android/hardware/biometrics/IBiometricService.aidl",
"core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl",
"core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl",
"core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl",
"core/java/android/hardware/display/IDisplayManager.aidl",
"core/java/android/hardware/display/IDisplayManagerCallback.aidl",

View File

@@ -30,19 +30,29 @@ import java.util.concurrent.Executor;
public interface BiometricAuthenticator {
/**
* No biometric methods or nothing has been enrolled.
* Move/expose these in BiometricPrompt if we ever want to allow applications to "blacklist"
* modalities when calling authenticate().
* @hide
*/
int TYPE_FINGERPRINT = 1;
int TYPE_NONE = 0;
/**
* Constant representing fingerprint.
* @hide
*/
int TYPE_FINGERPRINT = 1 << 0;
/**
* Constant representing iris.
* @hide
*/
int TYPE_IRIS = 2;
int TYPE_IRIS = 1 << 1;
/**
* Constant representing face.
* @hide
*/
int TYPE_FACE = 3;
int TYPE_FACE = 1 << 2;
/**
* Container for biometric data

View File

@@ -251,9 +251,40 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
private Executor mExecutor;
private AuthenticationCallback mAuthenticationCallback;
IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() {
private final IBiometricServiceReceiver mBiometricServiceReceiver =
new IBiometricServiceReceiver.Stub() {
@Override
public void onDialogDismissed(int reason) {
public void onAuthenticationSucceeded() throws RemoteException {
mExecutor.execute(() -> {
final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
mAuthenticationCallback.onAuthenticationSucceeded(result);
});
}
@Override
public void onAuthenticationFailed() throws RemoteException {
mExecutor.execute(() -> {
mAuthenticationCallback.onAuthenticationFailed();
});
}
@Override
public void onError(int error, String message) throws RemoteException {
mExecutor.execute(() -> {
mAuthenticationCallback.onAuthenticationError(error, message);
});
}
@Override
public void onAcquired(int acquireInfo, String message) throws RemoteException {
mExecutor.execute(() -> {
mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message);
});
}
@Override
public void onDialogDismissed(int reason) throws RemoteException {
// Check the reason and invoke OnClickListener(s) if necessary
if (reason == DISMISSED_REASON_POSITIVE) {
mPositiveButtonInfo.executor.execute(() -> {
@@ -267,40 +298,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
};
IBiometricServiceReceiver mBiometricServiceReceiver =
new IBiometricServiceReceiver.Stub() {
@Override
public void onAuthenticationSucceeded(long deviceId) throws RemoteException {
mExecutor.execute(() -> {
final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
mAuthenticationCallback.onAuthenticationSucceeded(result);
});
}
@Override
public void onAuthenticationFailed(long deviceId) throws RemoteException {
mExecutor.execute(() -> {
mAuthenticationCallback.onAuthenticationFailed();
});
}
@Override
public void onError(long deviceId, int error, String message)
throws RemoteException {
mExecutor.execute(() -> {
mAuthenticationCallback.onAuthenticationError(error, message);
});
}
@Override
public void onAcquired(long deviceId, int acquireInfo, String message) {
mExecutor.execute(() -> {
mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message);
});
}
};
private BiometricPrompt(Context context, Bundle bundle,
ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) {
mContext = context;
@@ -557,9 +554,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
mExecutor = executor;
mAuthenticationCallback = callback;
final long sessionId = crypto != null ? crypto.getOpId() : 0;
mService.authenticate(mToken, sessionId, userId,
mBiometricServiceReceiver, 0 /* flags */, mContext.getOpPackageName(),
mBundle, mDialogReceiver);
mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver,
mContext.getOpPackageName(), mBundle);
} catch (RemoteException e) {
Log.e(TAG, "Remote exception while authenticating", e);
mExecutor.execute(() -> {

View File

@@ -1,24 +0,0 @@
/*
* Copyright (C) 2018 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 android.hardware.biometrics;
/**
* Communication channel from the BiometricPrompt (SysUI) back to AuthenticationClient.
* @hide
*/
oneway interface IBiometricPromptReceiver {
void onDialogDismissed(int reason);
}

View File

@@ -18,7 +18,6 @@ package android.hardware.biometrics;
import android.os.Bundle;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceReceiver;
/**
@@ -32,8 +31,7 @@ interface IBiometricService {
// Requests authentication. The service choose the appropriate biometric to use, and show
// the corresponding BiometricDialog.
void authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, int flags, String opPackageName,
in Bundle bundle, IBiometricPromptReceiver dialogReceiver);
IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle);
// Cancel authentication for the given sessionId
void cancelAuthentication(IBinder token, String opPackageName);
@@ -46,4 +44,8 @@ interface IBiometricService {
// Explicitly set the active user.
void setActiveUser(int userId);
// Notify BiometricService when <Biometric>Service is ready to start the prepared client.
// Client lifecycle is still managed in <Biometric>Service.
void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId);
}

View File

@@ -16,12 +16,18 @@
package android.hardware.biometrics;
/**
* Communication channel from the BiometricService back to BiometricPrompt.
* Communication channel from BiometricService back to BiometricPrompt
* @hide
*/
oneway interface IBiometricServiceReceiver {
void onAuthenticationSucceeded(long deviceId);
void onAuthenticationFailed(long deviceId);
void onError(long deviceId, int error, String message);
void onAcquired(long deviceId, int acquiredInfo, String message);
// Notify BiometricPrompt that authentication was successful
void onAuthenticationSucceeded();
// Noties that authentication failed.
void onAuthenticationFailed();
// Notify BiometricPrompt that an error has occurred.
void onError(int error, String message);
// Notifies that a biometric has been acquired.
void onAcquired(int acquiredInfo, String message);
// Notifies that the SystemUI dialog has been dismissed.
void onDialogDismissed(int reason);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2018 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 android.hardware.biometrics;
/**
* Communication channel from
* 1) BiometricDialogImpl (SysUI) back to BiometricService
* 2) <Biometric>Service back to BiometricService
* Receives messages from the above and does some handling before forwarding to BiometricPrompt
* via IBiometricServiceReceiver.
* @hide
*/
oneway interface IBiometricServiceReceiverInternal {
// Notify BiometricService that authentication was successful. If user confirmation is required,
// the auth token must be submitted into KeyStore.
void onAuthenticationSucceeded(boolean requireConfirmation, in byte[] token);
// Notify BiometricService that an error has occurred.
void onAuthenticationFailed(int cookie, boolean requireConfirmation);
// Notify BiometricService than an error has occured. Forward to the correct receiver depending
// on the cookie.
void onError(int cookie, int error, String message);
// Notifies that a biometric has been acquired.
void onAcquired(int acquiredInfo, String message);
// Notifies that the SystemUI dialog has been dismissed.
void onDialogDismissed(int reason);
// Notifies that the user has pressed the "try again" button on SystemUI
void onTryAgainPressed();
}

View File

@@ -15,9 +15,7 @@
*/
package android.hardware.face;
import android.os.Bundle;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.face.IFaceServiceReceiver;
import android.hardware.face.Face;
@@ -32,19 +30,24 @@ interface IFaceService {
void authenticate(IBinder token, long sessionId,
IFaceServiceReceiver receiver, int flags, String opPackageName);
// This method invokes the BiometricDialog. The arguments are almost the same as above,
// but should only be called from (BiometricPromptService).
void authenticateFromService(boolean requireConfirmation, IBinder token, long sessionId,
int userId, IBiometricServiceReceiver receiver, int flags, String opPackageName,
in Bundle bundle, IBiometricPromptReceiver dialogReceiver,
int callingUid, int callingPid, int callingUserId);
// This method prepares the service to start authenticating, but doesn't start authentication.
// This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
// called from BiometricService. The additional uid, pid, userId arguments should be determined
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
void prepareForAuthentication(boolean requireConfirmation, IBinder token, long sessionId,
int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
int cookie, int callingUid, int callingPid, int callingUserId);
// Starts authentication with the previously prepared client.
void startPreparedClient(int cookie);
// Cancel authentication for the given sessionId
void cancelAuthentication(IBinder token, String opPackageName);
// Same as above, with extra arguments.
void cancelAuthenticationFromService(IBinder token, String opPackageName,
int callingUid, int callingPid, int callingUserId);
int callingUid, int callingPid, int callingUserId, boolean fromClient);
// Start face enrollment
void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver,

View File

@@ -15,9 +15,7 @@
*/
package android.hardware.fingerprint;
import android.os.Bundle;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -35,22 +33,25 @@ interface IFingerprintService {
void authenticate(IBinder token, long sessionId, int userId,
IFingerprintServiceReceiver receiver, int flags, String opPackageName);
// This method invokes the BiometricDialog. The arguments are almost the same as above, except
// this is protected by the MANAGE_BIOMETRIC signature permission. This method should only be
// called from BiometricPromptService. The additional uid, pid, userId arguments should be
// determined by BiometricPromptService.
void authenticateFromService(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, int flags, String opPackageName,
in Bundle bundle, IBiometricPromptReceiver dialogReceiver,
// This method prepares the service to start authenticating, but doesn't start authentication.
// This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
// called from BiometricService. The additional uid, pid, userId arguments should be determined
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
void prepareForAuthentication(IBinder token, long sessionId, int userId,
IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, int cookie,
int callingUid, int callingPid, int callingUserId);
// Starts authentication with the previously prepared client.
void startPreparedClient(int cookie);
// Cancel authentication for the given sessionId
void cancelAuthentication(IBinder token, String opPackageName);
// Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes
// an additional uid, pid, userid.
void cancelAuthenticationFromService(IBinder token, String opPackageName,
int callingUid, int callingPid, int callingUserId);
int callingUid, int callingPid, int callingUserId, boolean fromClient);
// Start fingerprint enrollment
void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver,

View File

@@ -18,7 +18,7 @@ package com.android.internal.statusbar;
import android.content.ComponentName;
import android.graphics.Rect;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
@@ -141,7 +141,7 @@ oneway interface IStatusBar
void showShutdownUi(boolean isReboot, String reason);
// Used to show the dialog when BiometricService starts authentication
void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type,
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
void onBiometricAuthenticated();
@@ -151,4 +151,6 @@ oneway interface IStatusBar
void onBiometricError(String error);
// Used to hide the biometric dialog when the AuthenticationClient is stopped
void hideBiometricDialog();
// Used to request the "try again" button for authentications which requireConfirmation=true
void showBiometricTryAgain();
}

View File

@@ -21,7 +21,7 @@ import android.content.ComponentName;
import android.graphics.Rect;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
@@ -92,7 +92,7 @@ interface IStatusBarService
void showPinningEscapeToast();
// Used to show the dialog when BiometricService starts authentication
void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type,
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
void onBiometricAuthenticated();
@@ -102,4 +102,6 @@ interface IStatusBarService
void onBiometricError(String error);
// Used to hide the biometric dialog when the AuthenticationClient is stopped
void hideBiometricDialog();
// Used to request the "try again" button for authentications which requireConfirmation=true
void showBiometricTryAgain();
}

View File

@@ -1444,9 +1444,14 @@
<!-- Title shown when the system-provided biometric dialog is shown, asking the user to authenticate. [CHAR LIMIT=40] -->
<string name="biometric_dialog_default_title">Application <xliff:g id="app" example="Gmail">%s</xliff:g> wants to authenticate.</string>
<!-- Message shown when biometric hardware is not available [CHAR LIMIT=50] -->
<string name="biometric_error_hw_unavailable">Biometric hardware unavailable</string>
<!-- Message shown when biometric authentication was canceled by the user [CHAR LIMIT=50] -->
<string name="biometric_error_user_canceled">Authentication canceled</string>
<!-- Message shown by the biometric dialog when biometric is not recognized -->
<string name="biometric_not_recognized">Not recognized</string>
<!-- Message shown when biometric authentication has been canceled [CHAR LIMIT=50] -->
<string name="biometric_error_canceled">Authentication canceled</string>
<!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized -->
<string name="fingerprint_acquired_partial">Partial fingerprint detected. Please try again.</string>
@@ -1462,8 +1467,6 @@
<string-array name="fingerprint_acquired_vendor">
</string-array>
<!-- Message shown by the biometric dialog when biometric is not recognized -->
<string name="biometric_not_recognized">Not recognized</string>
<!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] -->
<string name="fingerprint_authenticated">Fingerprint authenticated</string>
<!-- Accessibility message announced when a face has been authenticated [CHAR LIMIT=NONE] -->

View File

@@ -2406,7 +2406,9 @@
<!-- Biometric messages -->
<java-symbol type="string" name="biometric_dialog_default_title" />
<java-symbol type="string" name="biometric_error_hw_unavailable" />
<java-symbol type="string" name="biometric_error_user_canceled" />
<java-symbol type="string" name="biometric_not_recognized" />
<java-symbol type="string" name="biometric_error_canceled" />
<!-- Fingerprint messages -->
<java-symbol type="string" name="fingerprint_error_unable_to_process" />

View File

@@ -160,6 +160,15 @@
android:maxLines="2"
android:text="@string/biometric_dialog_confirm"
android:visibility="gone"/>
<!-- Try Again Button -->
<Button android:id="@+id/button_try_again"
android:layout_width="wrap_content"
android:layout_height="match_parent"
style="@*android:style/Widget.DeviceDefault.Button.Colored"
android:gravity="center"
android:maxLines="2"
android:text="@string/biometric_dialog_try_again"
android:visibility="gone"/>
<Space android:id="@+id/rightSpacer"
android:layout_width="12dip"
android:layout_height="match_parent"

View File

@@ -272,6 +272,8 @@
<string name="accessibility_biometric_dialog_help_area">Help message area</string>
<!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] -->
<string name="biometric_dialog_confirm">Confirm</string>
<!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR_LIMIT=30] -->
<string name="biometric_dialog_try_again">Try again</string>
<!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
<string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>

View File

@@ -21,7 +21,7 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -52,15 +52,20 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
private static final int MSG_BUTTON_NEGATIVE = 6;
private static final int MSG_USER_CANCELED = 7;
private static final int MSG_BUTTON_POSITIVE = 8;
private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9;
private static final int MSG_TRY_AGAIN_PRESSED = 10;
private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view
private SomeArgs mCurrentDialogArgs;
private BiometricDialogView mCurrentDialog;
private WindowManager mWindowManager;
private IBiometricPromptReceiver mReceiver;
private IBiometricServiceReceiverInternal mReceiver;
private boolean mDialogShowing;
private Callback mCallback = new Callback();
private boolean mTryAgainShowing; // No good place to save state before config change :/
private boolean mConfirmShowing; // No good place to save state before config change :/
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -89,6 +94,15 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
case MSG_BUTTON_POSITIVE:
handleButtonPositive();
break;
case MSG_BIOMETRIC_SHOW_TRY_AGAIN:
handleShowTryAgain();
break;
case MSG_TRY_AGAIN_PRESSED:
handleTryAgainPressed();
break;
default:
Log.w(TAG, "Unknown message: " + msg.what);
break;
}
}
};
@@ -96,7 +110,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
private class Callback implements DialogViewCallback {
@Override
public void onUserCanceled() {
mHandler.obtainMessage(BiometricDialogImpl.MSG_USER_CANCELED).sendToTarget();
mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget();
}
@Override
@@ -107,12 +121,17 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
@Override
public void onNegativePressed() {
mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_NEGATIVE).sendToTarget();
mHandler.obtainMessage(MSG_BUTTON_NEGATIVE).sendToTarget();
}
@Override
public void onPositivePressed() {
mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_POSITIVE).sendToTarget();
mHandler.obtainMessage(MSG_BUTTON_POSITIVE).sendToTarget();
}
@Override
public void onTryAgainPressed() {
mHandler.obtainMessage(MSG_TRY_AGAIN_PRESSED).sendToTarget();
}
}
@@ -139,13 +158,14 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
}
@Override
public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type,
boolean requireConfirmation, int userId) {
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) {
if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type);
// Remove these messages as they are part of the previous client
mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
mHandler.removeMessages(MSG_BIOMETRIC_HELP);
mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED);
mHandler.removeMessages(MSG_HIDE_DIALOG);
SomeArgs args = SomeArgs.obtain();
args.arg1 = bundle;
args.arg2 = receiver;
@@ -179,6 +199,12 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
}
@Override
public void showBiometricTryAgain() {
if (DEBUG) Log.d(TAG, "showBiometricTryAgain");
mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget();
}
private void handleShowDialog(SomeArgs args, boolean skipAnimation) {
mCurrentDialogArgs = args;
final int type = args.argi1;
@@ -193,11 +219,13 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
Log.w(TAG, "Dialog already showing");
return;
}
mReceiver = (IBiometricPromptReceiver) args.arg2;
mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
mCurrentDialog.setBundle((Bundle)args.arg1);
mCurrentDialog.setRequireConfirmation((boolean) args.arg3);
mCurrentDialog.setUserId(args.argi2);
mCurrentDialog.setSkipIntro(skipAnimation);
mCurrentDialog.setPendingTryAgain(mTryAgainShowing);
mCurrentDialog.setPendingConfirm(mConfirmShowing);
mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
mDialogShowing = true;
}
@@ -209,7 +237,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
mContext.getResources()
.getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
if (mCurrentDialog.requiresConfirmation()) {
mCurrentDialog.showConfirmationButton();
mConfirmShowing = true;
mCurrentDialog.showConfirmationButton(true /* show */);
} else {
handleHideDialog(false /* userCanceled */);
}
@@ -226,6 +255,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
if (DEBUG) Log.d(TAG, "Dialog already dismissed");
return;
}
mTryAgainShowing = false;
mCurrentDialog.showErrorMessage(error);
}
@@ -246,6 +276,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
}
mReceiver = null;
mDialogShowing = false;
mConfirmShowing = false;
mTryAgainShowing = false;
mCurrentDialog.startDismiss();
}
@@ -259,6 +291,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling negative button", e);
}
mTryAgainShowing = false;
handleHideDialog(false /* userCanceled */);
}
@@ -272,13 +305,31 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling positive button", e);
}
mConfirmShowing = false;
handleHideDialog(false /* userCanceled */);
}
private void handleUserCanceled() {
mTryAgainShowing = false;
mConfirmShowing = false;
handleHideDialog(true /* userCanceled */);
}
private void handleShowTryAgain() {
mCurrentDialog.showTryAgainButton(true /* show */);
mTryAgainShowing = true;
}
private void handleTryAgainPressed() {
try {
mCurrentDialog.clearTemporaryMessage();
mTryAgainShowing = false;
mReceiver.onTryAgainPressed();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when handling try again", e);
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);

View File

@@ -87,6 +87,9 @@ public abstract class BiometricDialogView extends LinearLayout {
protected boolean mRequireConfirmation;
private int mUserId; // used to determine if we should show work background
private boolean mPendingShowTryAgain;
private boolean mPendingShowConfirm;
protected abstract void updateIcon(int lastState, int newState);
protected abstract int getHintStringResourceId();
protected abstract int getAuthenticatedAccessibilityResourceId();
@@ -178,6 +181,7 @@ public abstract class BiometricDialogView extends LinearLayout {
final Button negative = mLayout.findViewById(R.id.button2);
final Button positive = mLayout.findViewById(R.id.button1);
final ImageView icon = mLayout.findViewById(R.id.biometric_icon);
final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
icon.setContentDescription(getResources().getString(getIconDescriptionResourceId()));
@@ -193,6 +197,11 @@ public abstract class BiometricDialogView extends LinearLayout {
mCallback.onPositivePressed();
});
tryAgain.setOnClickListener((View v) -> {
showTryAgainButton(false /* show */);
mCallback.onTryAgainPressed();
});
mLayout.setFocusableInTouchMode(true);
mLayout.requestFocus();
}
@@ -207,7 +216,6 @@ public abstract class BiometricDialogView extends LinearLayout {
final TextView subtitle = mLayout.findViewById(R.id.subtitle);
final TextView description = mLayout.findViewById(R.id.description);
final Button negative = mLayout.findViewById(R.id.button2);
final Button positive = mLayout.findViewById(R.id.button1);
final ImageView backgroundView = mLayout.findViewById(R.id.background);
if (mUserManager.isManagedProfile(mUserId)) {
@@ -233,8 +241,6 @@ public abstract class BiometricDialogView extends LinearLayout {
title.setText(titleText);
title.setSelected(true);
positive.setVisibility(View.INVISIBLE);
final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
if (TextUtils.isEmpty(subtitleText)) {
subtitle.setVisibility(View.GONE);
@@ -243,7 +249,8 @@ public abstract class BiometricDialogView extends LinearLayout {
subtitle.setText(subtitleText);
}
final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
final CharSequence descriptionText =
mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
if (TextUtils.isEmpty(descriptionText)) {
description.setVisibility(View.GONE);
} else {
@@ -253,6 +260,9 @@ public abstract class BiometricDialogView extends LinearLayout {
negative.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
showTryAgainButton(mPendingShowTryAgain);
showConfirmationButton(mPendingShowConfirm);
if (mWasForceRemoved || mSkipIntro) {
// Show the dialog immediately
mLayout.animate().cancel();
@@ -281,11 +291,17 @@ public abstract class BiometricDialogView extends LinearLayout {
public void startDismiss() {
mAnimatingAway = true;
// This is where final cleanup should occur.
final Runnable endActionRunnable = new Runnable() {
@Override
public void run() {
mWindowManager.removeView(BiometricDialogView.this);
mAnimatingAway = false;
// Set the icons / text back to normal state
handleClearMessage();
showTryAgainButton(false /* show */);
mPendingShowTryAgain = false;
mPendingShowConfirm = false;
}
};
@@ -345,9 +361,13 @@ public abstract class BiometricDialogView extends LinearLayout {
return mRequireConfirmation;
}
public void showConfirmationButton() {
public void showConfirmationButton(boolean show) {
final Button positive = mLayout.findViewById(R.id.button1);
positive.setVisibility(View.VISIBLE);
if (show) {
positive.setVisibility(View.VISIBLE);
} else {
positive.setVisibility(View.GONE);
}
}
public void setUserId(int userId) {
@@ -376,12 +396,18 @@ public abstract class BiometricDialogView extends LinearLayout {
BiometricPrompt.HIDE_DIALOG_DELAY);
}
public void clearTemporaryMessage() {
mHandler.removeMessages(MSG_CLEAR_MESSAGE);
mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget();
}
public void showHelpMessage(String message) {
showTemporaryMessage(message);
}
public void showErrorMessage(String error) {
showTemporaryMessage(error);
showTryAgainButton(false /* show */);
mCallback.onErrorShown();
}
@@ -390,6 +416,25 @@ public abstract class BiometricDialogView extends LinearLayout {
mLastState = newState;
}
public void showTryAgainButton(boolean show) {
final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
if (show) {
tryAgain.setVisibility(View.VISIBLE);
} else {
tryAgain.setVisibility(View.GONE);
}
}
// Set the state before the window is attached, so we know if the dialog should be started
// with or without the button. This is because there's no good onPause signal
public void setPendingTryAgain(boolean show) {
mPendingShowTryAgain = show;
}
public void setPendingConfirm(boolean show) {
mPendingShowConfirm = show;
}
public WindowManager.LayoutParams getLayoutParams() {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,

View File

@@ -43,4 +43,9 @@ public interface DialogViewCallback {
* should be dismissed.
*/
void onPositivePressed();
/**
* Invoked when the "try again" button is pressed.
*/
void onTryAgainPressed();
}

View File

@@ -21,7 +21,7 @@ import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS;
import android.app.StatusBarManager;
import android.content.ComponentName;
import android.graphics.Rect;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -96,6 +96,7 @@ public class CommandQueue extends IStatusBar.Stub {
private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT;
private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT;
private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT;
private static final int MSG_BIOMETRIC_TRY_AGAIN = 47 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -163,12 +164,13 @@ public class CommandQueue extends IStatusBar.Stub {
default void onRotationProposal(int rotation, boolean isValid) { }
default void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver,
default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) { }
default void onBiometricAuthenticated() { }
default void onBiometricHelp(String message) { }
default void onBiometricError(String error) { }
default void hideBiometricDialog() { }
default void showBiometricTryAgain() { }
}
@VisibleForTesting
@@ -523,8 +525,8 @@ public class CommandQueue extends IStatusBar.Stub {
}
@Override
public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type,
boolean requireConfirmation, int userId) {
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = bundle;
@@ -565,6 +567,13 @@ public class CommandQueue extends IStatusBar.Stub {
}
}
@Override
public void showBiometricTryAgain() {
synchronized (mLock) {
mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget();
}
}
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -774,7 +783,7 @@ public class CommandQueue extends IStatusBar.Stub {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showBiometricDialog(
(Bundle) someArgs.arg1,
(IBiometricPromptReceiver) someArgs.arg2,
(IBiometricServiceReceiverInternal) someArgs.arg2,
someArgs.argi1 /* type */,
(boolean) someArgs.arg3 /* requireConfirmation */,
someArgs.argi2 /* userId */);
@@ -816,6 +825,11 @@ public class CommandQueue extends IStatusBar.Stub {
mCallbacks.get(i).showPinningEscapeToast();
}
break;
case MSG_BIOMETRIC_TRY_AGAIN:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showBiometricTryAgain();
}
break;
}
}
}

View File

@@ -19,19 +19,11 @@ package com.android.server.biometrics;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.statusbar.IStatusBarService;
import java.util.ArrayList;
/**
@@ -39,88 +31,15 @@ import java.util.ArrayList;
*/
public abstract class AuthenticationClient extends ClientMonitor {
private long mOpId;
private Handler mHandler;
public abstract int handleFailedAttempt();
public abstract void resetFailedAttempts();
public abstract String getErrorString(int error, int vendorCode);
public abstract String getAcquiredString(int acquireInfo, int vendorCode);
/**
* @return one of {@link #TYPE_FINGERPRINT} {@link #TYPE_IRIS} or {@link #TYPE_FACE}
*/
public abstract int getBiometricType();
public static final int LOCKOUT_NONE = 0;
public static final int LOCKOUT_TIMED = 1;
public static final int LOCKOUT_PERMANENT = 2;
private final boolean mRequireConfirmation;
// Callback mechanism received from the client
// (BiometricPrompt -> BiometricPromptService -> <Biometric>Service -> AuthenticationClient)
private IBiometricPromptReceiver mDialogReceiverFromClient;
private Bundle mBundle;
private IStatusBarService mStatusBarService;
private boolean mInLockout;
private TokenEscrow mEscrow;
protected boolean mDialogDismissed;
/**
* Container that holds the identifier and authToken. For biometrics that require user
* confirmation, these should not be sent to their final destinations until the user confirms.
*/
class TokenEscrow {
final BiometricAuthenticator.Identifier mIdentifier;
final ArrayList<Byte> mToken;
TokenEscrow(BiometricAuthenticator.Identifier identifier, ArrayList<Byte> token) {
mIdentifier = identifier;
mToken = token;
}
BiometricAuthenticator.Identifier getIdentifier() {
return mIdentifier;
}
ArrayList<Byte> getToken() {
return mToken;
}
}
// Receives events from SystemUI and handles them before forwarding them to BiometricDialog
protected IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() {
@Override // binder call
public void onDialogDismissed(int reason) {
if (mBundle != null && mDialogReceiverFromClient != null) {
try {
if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) {
// Positive button is used by passive modalities as a "confirm" button,
// do not send to client
mDialogReceiverFromClient.onDialogDismissed(reason);
}
if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
0 /* vendorCode */);
} else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
// Have the service send the token to KeyStore, and send onAuthenticated
// to the application.
if (mEscrow != null) {
if (DEBUG) Slog.d(getLogTag(), "Confirmed");
addTokenToKeyStore(mEscrow.getToken());
notifyClientAuthenticationSucceeded(mEscrow.getIdentifier());
mEscrow = null;
onAuthenticationConfirmed();
} else {
Slog.e(getLogTag(), "Escrow is null!!!");
}
}
mDialogDismissed = true;
} catch (RemoteException e) {
Slog.e(getLogTag(), "Remote exception", e);
}
stop(true /* initiatedByClient */);
}
}
};
/**
* This method is called when authentication starts.
@@ -133,25 +52,13 @@ public abstract class AuthenticationClient extends ClientMonitor {
*/
public abstract void onStop();
/**
* This method is called when biometric authentication was confirmed by the user. The client
* should be removed.
*/
public abstract void onAuthenticationConfirmed();
public AuthenticationClient(Context context, Metrics metrics,
BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId,
boolean restricted, String owner, Bundle bundle,
IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService,
boolean requireConfirmation) {
boolean restricted, String owner, int cookie, boolean requireConfirmation) {
super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId,
restricted, owner);
restricted, owner, cookie);
mOpId = opId;
mBundle = bundle;
mDialogReceiverFromClient = dialogReceiver;
mStatusBarService = statusBarService;
mHandler = new Handler(Looper.getMainLooper());
mRequireConfirmation = requireConfirmation;
}
@@ -164,175 +71,99 @@ public abstract class AuthenticationClient extends ClientMonitor {
stop(false /* initiatedByClient */);
}
@Override
public boolean onAcquired(int acquiredInfo, int vendorCode) {
// If the dialog is showing, the client doesn't need to receive onAcquired messages.
if (mBundle != null) {
try {
if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
mStatusBarService.onBiometricHelp(getAcquiredString(acquiredInfo, vendorCode));
}
return false; // acquisition continues
} catch (RemoteException e) {
Slog.e(getLogTag(), "Remote exception when sending acquired message", e);
return true; // client failed
} finally {
// Good scans will keep the device awake
if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
notifyUserActivity();
}
}
} else {
return super.onAcquired(acquiredInfo, vendorCode);
}
}
@Override
public boolean onError(long deviceId, int error, int vendorCode) {
if (mDialogDismissed) {
// If user cancels authentication, the application has already received the
// ERROR_USER_CANCELED message from onDialogDismissed()
// and stopped the biometric hardware, so there is no need to send a
// ERROR_CANCELED message.
return true;
}
if (mBundle != null && error != BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED) {
try {
mStatusBarService.onBiometricError(getErrorString(error, vendorCode));
} catch (RemoteException e) {
Slog.e(getLogTag(), "Remote exception when sending error", e);
}
}
return super.onError(deviceId, error, vendorCode);
}
public void setTitleIfEmpty(CharSequence title) {
if (TextUtils.isEmpty(mBundle.getCharSequence(BiometricPrompt.KEY_TITLE))) {
mBundle.putCharSequence(BiometricPrompt.KEY_TITLE, title);
}
}
public boolean isBiometricPrompt() {
return mBundle != null;
return getCookie() != 0;
}
private void notifyClientAuthenticationSucceeded(BiometricAuthenticator.Identifier identifier)
throws RemoteException {
final BiometricServiceBase.ServiceListener listener = getListener();
// Explicitly have if/else here to make it super obvious in case the code is
// touched in the future.
if (!getIsRestricted()) {
listener.onAuthenticationSucceeded(
getHalDeviceId(), identifier, getTargetUserId());
} else {
listener.onAuthenticationSucceeded(
getHalDeviceId(), null, getTargetUserId());
}
}
private void addTokenToKeyStore(ArrayList<Byte> token) {
// Send the token to KeyStore
final byte[] byteToken = new byte[token.size()];
for (int i = 0; i < token.size(); i++) {
byteToken[i] = token.get(i);
}
KeyStore.getInstance().addAuthToken(byteToken);
public boolean getRequireConfirmation() {
return mRequireConfirmation;
}
@Override
public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,
boolean authenticated, ArrayList<Byte> token) {
if (authenticated) {
mAlreadyDone = true;
if (mRequireConfirmation) {
// Store the token so it can be sent to keystore after the user presses confirm
mEscrow = new TokenEscrow(identifier, token);
} else {
addTokenToKeyStore(token);
}
}
final BiometricServiceBase.ServiceListener listener = getListener();
mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated);
boolean result = false;
// If the biometric dialog is showing, notify authentication succeeded
if (mBundle != null) {
try {
if (authenticated) {
mStatusBarService.onBiometricAuthenticated();
} else {
mStatusBarService.onBiometricHelp(getContext().getResources().getString(
com.android.internal.R.string.biometric_not_recognized));
try {
if (authenticated) {
mAlreadyDone = true;
if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + getOwnerString()
+ ", ID:" + identifier.getBiometricId()
+ ", isBP: " + isBiometricPrompt()
+ ", listener: " + listener
+ ", requireConfirmation: " + mRequireConfirmation);
if (listener != null) {
vibrateSuccess();
}
} catch (RemoteException e) {
Slog.e(getLogTag(), "Failed to notify Authenticated:", e);
}
}
result = true;
resetFailedAttempts();
onStop();
final BiometricServiceBase.ServiceListener listener = getListener();
if (listener != null) {
try {
mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated);
if (!authenticated) {
listener.onAuthenticationFailed(getHalDeviceId());
} else {
if (DEBUG) {
Slog.v(getLogTag(), "onAuthenticated(owner=" + getOwnerString()
+ ", id=" + identifier.getBiometricId());
}
if (!mRequireConfirmation) {
notifyClientAuthenticationSucceeded(identifier);
}
final byte[] byteToken = new byte[token.size()];
for (int i = 0; i < token.size(); i++) {
byteToken[i] = token.get(i);
}
} catch (RemoteException e) {
Slog.w(getLogTag(), "Failed to notify Authenticated:", e);
result = true; // client failed
}
} else {
result = true; // client not listening
}
if (!authenticated) {
if (listener != null) {
vibrateError();
}
// allow system-defined limit of number of attempts before giving up
int lockoutMode = handleFailedAttempt();
if (lockoutMode != LOCKOUT_NONE) {
try {
mInLockout = true;
Slog.w(getLogTag(), "Forcing lockout (fp driver code should do this!), mode(" +
lockoutMode + ")");
if (isBiometricPrompt() && listener != null) {
// BiometricService will add the token to keystore
listener.onAuthenticationSucceededInternal(mRequireConfirmation, byteToken);
} else if (!isBiometricPrompt() && listener != null) {
KeyStore.getInstance().addAuthToken(byteToken);
try {
// Explicitly have if/else here to make it super obvious in case the code is
// touched in the future.
if (!getIsRestricted()) {
listener.onAuthenticationSucceeded(
getHalDeviceId(), identifier, getTargetUserId());
} else {
listener.onAuthenticationSucceeded(
getHalDeviceId(), null, getTargetUserId());
}
} catch (RemoteException e) {
Slog.e(getLogTag(), "Remote exception", e);
}
} else {
// Client not listening
Slog.w(getLogTag(), "Client not listening");
result = true;
}
} else {
if (listener != null) {
vibrateError();
}
// Allow system-defined limit of number of attempts before giving up
final int lockoutMode = handleFailedAttempt();
if (lockoutMode != LOCKOUT_NONE) {
Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode("
+ lockoutMode + ")");
stop(false);
int errorCode = lockoutMode == LOCKOUT_TIMED ?
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
// Send the lockout message to the system dialog
if (mBundle != null) {
mStatusBarService.onBiometricError(
getErrorString(errorCode, 0 /* vendorCode */));
mHandler.postDelayed(() -> {
try {
listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.w(getLogTag(), "RemoteException while sending error");
}
}, BiometricPrompt.HIDE_DIALOG_DELAY);
} else {
listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
final int errorCode = lockoutMode == LOCKOUT_TIMED
? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
: BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
if (listener != null) {
listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */,
getCookie());
}
} else {
// Don't send onAuthenticationFailed if we're in lockout, it causes a
// janky UI on Keyguard/BiometricPrompt since "authentication failed"
// will show briefly and be replaced by "device locked out" message.
if (listener != null) {
if (isBiometricPrompt()) {
listener.onAuthenticationFailedInternal(getCookie(),
getRequireConfirmation());
} else {
listener.onAuthenticationFailed(getHalDeviceId());
}
}
} catch (RemoteException e) {
Slog.w(getLogTag(), "Failed to notify lockout:", e);
}
result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
}
result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
} else {
if (listener != null) {
vibrateSuccess();
}
// we have a valid biometric that doesn't require confirmation, done
result |= !mRequireConfirmation;
resetFailedAttempts();
onStop();
} catch (RemoteException e) {
Slog.e(getLogTag(), "Remote exception", e);
result = true;
}
return result;
}
@@ -353,16 +184,6 @@ public abstract class AuthenticationClient extends ClientMonitor {
return result;
}
if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");
// If authenticating with system dialog, show the dialog
if (mBundle != null) {
try {
mStatusBarService.showBiometricDialog(mBundle, mDialogReceiver,
getBiometricType(), mRequireConfirmation, getTargetUserId());
} catch (RemoteException e) {
Slog.e(getLogTag(), "Unable to show biometric dialog", e);
}
}
} catch (RemoteException e) {
Slog.e(getLogTag(), "startAuthentication failed", e);
return ERROR_ESRCH;
@@ -390,18 +211,6 @@ public abstract class AuthenticationClient extends ClientMonitor {
} catch (RemoteException e) {
Slog.e(getLogTag(), "stopAuthentication failed", e);
return ERROR_ESRCH;
} finally {
// If the user already cancelled authentication (via some interaction with the
// dialog, we do not need to hide it since it's already hidden.
// If the device is in lockout, don't hide the dialog - it will automatically hide
// after BiometricPrompt.HIDE_DIALOG_DELAY
if (mBundle != null && !mDialogDismissed && !mInLockout) {
try {
mStatusBarService.hideBiometricDialog();
} catch (RemoteException e) {
Slog.e(getLogTag(), "Unable to hide biometric dialog", e);
}
}
}
mAlreadyCancelled = true;

View File

@@ -19,9 +19,17 @@ package com.android.server.biometrics;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.IActivityTaskManager;
import android.app.TaskStackListener;
import android.app.UserSwitchObserver;
import android.content.ContentResolver;
import android.content.Context;
@@ -32,9 +40,9 @@ import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.face.FaceManager;
import android.hardware.face.IFaceService;
import android.hardware.fingerprint.FingerprintManager;
@@ -50,14 +58,21 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.SystemService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* System service that arbitrates the modality for BiometricPrompt to use.
@@ -66,32 +81,10 @@ public class BiometricService extends SystemService {
private static final String TAG = "BiometricService";
/**
* No biometric methods or nothing has been enrolled.
* Move/expose these in BiometricPrompt if we ever want to allow applications to "blacklist"
* modalities when calling authenticate().
*/
private static final int BIOMETRIC_NONE = 0;
/**
* Constant representing fingerprint.
*/
private static final int BIOMETRIC_FINGERPRINT = 1 << 0;
/**
* Constant representing iris.
*/
private static final int BIOMETRIC_IRIS = 1 << 1;
/**
* Constant representing face.
*/
private static final int BIOMETRIC_FACE = 1 << 2;
private static final int[] FEATURE_ID = {
BIOMETRIC_FINGERPRINT,
BIOMETRIC_IRIS,
BIOMETRIC_FACE
TYPE_FINGERPRINT,
TYPE_IRIS,
TYPE_FACE
};
private final AppOpsManager mAppOps;
@@ -242,10 +235,367 @@ public class BiometricService extends SystemService {
*/
private final class BiometricServiceWrapper extends IBiometricService.Stub {
/**
* Authentication either just called and we have not transitioned to the CALLED state, or
* authentication terminated (success or error).
*/
private static final int STATE_AUTH_IDLE = 0;
/**
* Authentication was called and we are waiting for the <Biometric>Services to return their
* cookies before starting the hardware and showing the BiometricPrompt.
*/
private static final int STATE_AUTH_CALLED = 1;
/**
* Authentication started, BiometricPrompt is showing and the hardware is authenticating.
*/
private static final int STATE_AUTH_STARTED = 2;
/**
* Authentication is paused, waiting for the user to press "try again" button. Since the
* try again button requires us to cancel authentication, this represents the state where
* ERROR_CANCELED is not received yet.
*/
private static final int STATE_AUTH_PAUSED = 3;
/**
* Same as above, except the ERROR_CANCELED has been received.
*/
private static final int STATE_AUTH_PAUSED_CANCELED = 4;
/**
* Authentication is successful, but we're waiting for the user to press "confirm" button.
*/
private static final int STATE_AUTH_PENDING_CONFIRM = 5;
final class AuthSession {
// Map of Authenticator/Cookie pairs. We expect to receive the cookies back from
// <Biometric>Services before we can start authenticating. Pairs that have been returned
// are moved to mModalitiesMatched.
final HashMap<Integer, Integer> mModalitiesWaiting;
// Pairs that have been matched.
final HashMap<Integer, Integer> mModalitiesMatched = new HashMap<>();
// The following variables are passed to authenticateInternal, which initiates the
// appropriate <Biometric>Services.
final IBinder mToken;
final long mSessionId;
final int mUserId;
// Original receiver from BiometricPrompt.
final IBiometricServiceReceiver mClientReceiver;
final String mOpPackageName;
// Info to be shown on BiometricDialog when all cookies are returned.
final Bundle mBundle;
final int mCallingUid;
final int mCallingPid;
final int mCallingUserId;
// Continue authentication with the same modality/modalities after "try again" is
// pressed
final int mModality;
// The current state, which can be either idle, called, or started
private int mState = STATE_AUTH_IDLE;
// For explicit confirmation, do not send to keystore until the user has confirmed
// the authentication.
byte[] mTokenEscrow;
AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId,
int userId, IBiometricServiceReceiver receiver, String opPackageName,
Bundle bundle, int callingUid, int callingPid, int callingUserId,
int modality) {
mModalitiesWaiting = modalities;
mToken = token;
mSessionId = sessionId;
mUserId = userId;
mClientReceiver = receiver;
mOpPackageName = opPackageName;
mBundle = bundle;
mCallingUid = callingUid;
mCallingPid = callingPid;
mCallingUserId = callingUserId;
mModality = modality;
}
boolean containsCookie(int cookie) {
if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) {
return true;
}
if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) {
return true;
}
return false;
}
}
final class BiometricTaskStackListener extends TaskStackListener {
@Override
public void onTaskStackChanged() {
try {
final List<ActivityManager.RunningTaskInfo> runningTasks =
mActivityTaskManager.getTasks(1);
if (!runningTasks.isEmpty()) {
final String topPackage = runningTasks.get(0).topActivity.getPackageName();
if (mCurrentAuthSession != null
&& !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName)
&& mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
// We only care about this state, since <Biometric>Service will
// cancel any client that's still in STATE_AUTH_STARTED
mStatusBarService.hideBiometricDialog();
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
mCurrentAuthSession.mClientReceiver.onError(
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
getContext().getString(
com.android.internal.R.string.biometric_error_canceled)
);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
}
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to get running tasks", e);
}
}
}
private final IActivityTaskManager mActivityTaskManager = getContext().getSystemService(
ActivityTaskManager.class).getService();
private final IStatusBarService mStatusBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
private final BiometricTaskStackListener mTaskStackListener =
new BiometricTaskStackListener();
private final Random mRandom = new Random();
// The current authentication session, null if idle/done. We need to track both the current
// and pending sessions since errors may be sent to either.
private AuthSession mCurrentAuthSession;
private AuthSession mPendingAuthSession;
// Wrap the client's receiver so we can do things with the BiometricDialog first
private final IBiometricServiceReceiverInternal mInternalReceiver =
new IBiometricServiceReceiverInternal.Stub() {
@Override
public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token)
throws RemoteException {
try {
if (!requireConfirmation) {
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
KeyStore.getInstance().addAuthToken(token);
mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
} else {
// Store the auth token and submit it to keystore after the confirmation
// button has been pressed.
mCurrentAuthSession.mTokenEscrow = token;
mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM;
}
// Notify SysUI that the biometric has been authenticated. SysUI already knows
// the implicit/explicit state and will react accordingly.
mStatusBarService.onBiometricAuthenticated();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
@Override
public void onAuthenticationFailed(int cookie, boolean requireConfirmation)
throws RemoteException {
try {
mStatusBarService.onBiometricHelp(getContext().getResources().getString(
com.android.internal.R.string.biometric_not_recognized));
if (requireConfirmation) {
mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
mStatusBarService.showBiometricTryAgain();
// Cancel authentication. Skip the token/package check since we are
// cancelling from system server. The interface is permission protected so
// this is fine.
cancelInternal(null /* token */, null /* package */,
false /* fromClient */);
}
mCurrentAuthSession.mClientReceiver.onAuthenticationFailed();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
@Override
public void onError(int cookie, int error, String message) throws RemoteException {
Slog.d(TAG, "Error: " + error + " cookie: " + cookie);
// Errors can either be from the current auth session or the pending auth session.
// The pending auth session may receive errors such as ERROR_LOCKOUT before
// it becomes the current auth session. Similarly, the current auth session may
// receive errors such as ERROR_CANCELED while the pending auth session is preparing
// to be started. Thus we must match error messages with their cookies to be sure
// of their intended receivers.
try {
if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
mStatusBarService.onBiometricError(message);
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
mCurrentAuthSession.mClientReceiver.onError(error, message);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
mStatusBarService.hideBiometricDialog();
} else {
// Send errors after the dialog is dismissed.
mHandler.postDelayed(() -> {
try {
mCurrentAuthSession.mClientReceiver.onError(error, message);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}, BiometricPrompt.HIDE_DIALOG_DELAY);
}
} else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED
|| mCurrentAuthSession.mState == STATE_AUTH_PAUSED_CANCELED) {
if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED
&& error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
// Skip the first ERROR_CANCELED message when this happens, since
// "try again" requires us to cancel authentication but keep
// the prompt showing.
mCurrentAuthSession.mState = STATE_AUTH_PAUSED_CANCELED;
} else {
// In the "try again" state, we should forward canceled errors to
// the client and and clean up.
mCurrentAuthSession.mClientReceiver.onError(error, message);
mStatusBarService.onBiometricError(message);
mActivityTaskManager.unregisterTaskStackListener(
mTaskStackListener);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
}
} else {
Slog.e(TAG, "Impossible session error state: "
+ mCurrentAuthSession.mState);
}
} else if (mPendingAuthSession != null
&& mPendingAuthSession.containsCookie(cookie)) {
if (mPendingAuthSession.mState == STATE_AUTH_CALLED) {
mPendingAuthSession.mClientReceiver.onError(error, message);
mPendingAuthSession.mState = STATE_AUTH_IDLE;
mPendingAuthSession = null;
} else {
Slog.e(TAG, "Impossible pending session error state: "
+ mPendingAuthSession.mState);
}
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
@Override
public void onAcquired(int acquiredInfo, String message) throws RemoteException {
if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
try {
mStatusBarService.onBiometricHelp(message);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
}
@Override
public void onDialogDismissed(int reason) throws RemoteException {
if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) {
// Positive button is used by passive modalities as a "confirm" button,
// do not send to client
mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason);
// Cancel authentication. Skip the token/package check since we are cancelling
// from system server. The interface is permission protected so this is fine.
cancelInternal(null /* token */, null /* package */, false /* fromClient */);
}
if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
mCurrentAuthSession.mClientReceiver.onError(
BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
getContext().getString(
com.android.internal.R.string.biometric_error_user_canceled));
} else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
// Have the service send the token to KeyStore, and send onAuthenticated
// to the application
KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow);
mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
}
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
}
@Override
public void onTryAgainPressed() {
Slog.d(TAG, "onTryAgainPressed");
// No need to check permission, since it can only be invoked by SystemUI
// (or system server itself).
mHandler.post(() -> {
authenticateInternal(mCurrentAuthSession.mToken,
mCurrentAuthSession.mSessionId,
mCurrentAuthSession.mUserId,
mCurrentAuthSession.mClientReceiver,
mCurrentAuthSession.mOpPackageName,
mCurrentAuthSession.mBundle,
mCurrentAuthSession.mCallingUid,
mCurrentAuthSession.mCallingPid,
mCurrentAuthSession.mCallingUserId,
mCurrentAuthSession.mModality);
});
}
};
@Override // Binder call
public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) {
checkInternalPermission();
Iterator it = mPendingAuthSession.mModalitiesWaiting.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
if (pair.getValue() == cookie) {
mPendingAuthSession.mModalitiesMatched.put(pair.getKey(), pair.getValue());
mPendingAuthSession.mModalitiesWaiting.remove(pair.getKey());
Slog.d(TAG, "Matched cookie: " + cookie + ", "
+ mPendingAuthSession.mModalitiesWaiting.size() + " remaining");
break;
}
}
if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) {
final boolean mContinuing = mCurrentAuthSession != null
&& mCurrentAuthSession.mState == STATE_AUTH_PAUSED;
mCurrentAuthSession = mPendingAuthSession;
mPendingAuthSession = null;
mCurrentAuthSession.mState = STATE_AUTH_STARTED;
try {
int modality = TYPE_NONE;
it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
if (pair.getKey() == TYPE_FINGERPRINT) {
mFingerprintService.startPreparedClient(pair.getValue());
} else if (pair.getKey() == TYPE_IRIS) {
Slog.e(TAG, "Iris unsupported");
} else if (pair.getKey() == TYPE_FACE) {
mFaceService.startPreparedClient(pair.getValue());
} else {
Slog.e(TAG, "Unknown modality: " + pair.getKey());
}
modality |= pair.getKey();
}
if (!mContinuing) {
mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
mInternalReceiver, modality, requireConfirmation, userId);
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
}
@Override // Binder call
public void authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, int flags, String opPackageName,
Bundle bundle, IBiometricPromptReceiver dialogReceiver) throws RemoteException {
IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle)
throws RemoteException {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
@@ -261,16 +611,45 @@ public class BiometricService extends SystemService {
checkInternalPermission();
}
if (token == null || receiver == null || opPackageName == null || bundle == null
|| dialogReceiver == null) {
if (token == null || receiver == null || opPackageName == null || bundle == null) {
Slog.e(TAG, "Unable to authenticate, one or more null arguments");
return;
}
// Check the usage of this in system server. Need to remove this check if it becomes
// a public API.
if (bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) {
final boolean useDefaultTitle =
bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false);
if (useDefaultTitle) {
checkInternalPermission();
// Set the default title if necessary
try {
if (useDefaultTitle) {
final List<ActivityManager.RunningAppProcessInfo> procs =
ActivityManager.getService().getRunningAppProcesses();
for (int i = 0; i < procs.size(); i++) {
final ActivityManager.RunningAppProcessInfo info = procs.get(i);
if (info.uid == callingUid
&& info.importance == IMPORTANCE_FOREGROUND) {
PackageManager pm = getContext().getPackageManager();
final CharSequence label = pm.getApplicationLabel(
pm.getApplicationInfo(info.processName,
PackageManager.GET_META_DATA));
final String title = getContext()
.getString(R.string.biometric_dialog_default_title, label);
if (TextUtils.isEmpty(
bundle.getCharSequence(BiometricPrompt.KEY_TITLE))) {
bundle.putCharSequence(BiometricPrompt.KEY_TITLE, title);
}
break;
}
}
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Name not found", e);
}
}
mHandler.post(() -> {
@@ -285,13 +664,13 @@ public class BiometricService extends SystemService {
getContext().getString(R.string.biometric_error_hw_unavailable);
switch (error) {
case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
receiver.onError(0 /* deviceId */, error, hardwareUnavailable);
receiver.onError(error, hardwareUnavailable);
break;
case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
receiver.onError(0 /* deviceId */, error, hardwareUnavailable);
receiver.onError(error, hardwareUnavailable);
break;
case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
receiver.onError(0 /* deviceId */, error,
receiver.onError(error,
getErrorString(modality, error, 0 /* vendorCode */));
break;
default:
@@ -304,60 +683,91 @@ public class BiometricService extends SystemService {
return;
}
// Actually start authentication
mCurrentModality = modality;
try {
// No polymorphism :(
if (mCurrentModality == BIOMETRIC_FINGERPRINT) {
mFingerprintService.authenticateFromService(token, sessionId, userId,
receiver, flags, opPackageName, bundle, dialogReceiver,
callingUid, callingPid, callingUserId);
} else if (mCurrentModality == BIOMETRIC_IRIS) {
Slog.w(TAG, "Unsupported modality");
} else if (mCurrentModality == BIOMETRIC_FACE) {
mFaceService.authenticateFromService(true /* requireConfirmation */,
token, sessionId, userId, receiver, flags, opPackageName,
bundle, dialogReceiver, callingUid, callingPid, callingUserId);
} else {
Slog.w(TAG, "Unsupported modality");
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to start authentication", e);
}
// Actually start authentication
authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
callingUid, callingPid, callingUserId, modality);
});
}
/**
* authenticate() (above) which is called from BiometricPrompt determines which
* modality/modalities to start authenticating with. authenticateInternal() should only be
* used for:
* 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is,
* invoked, shortly after which BiometricPrompt is shown and authentication starts
* 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown
* and the user has pressed "try again"
*/
private void authenticateInternal(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
int callingUid, int callingPid, int callingUserId, int modality) {
try {
// Generate random cookies to pass to the services that should prepare to start
// authenticating. Store the cookie here and wait for all services to "ack"
// with the cookie. Once all cookies are received, we can show the prompt
// and let the services start authenticating. The cookie should be non-zero.
final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
Slog.d(TAG, "Creating auth session. Modality: " + modality
+ ", cookie: " + cookie);
final HashMap<Integer, Integer> authenticators = new HashMap<>();
authenticators.put(modality, cookie);
mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId,
receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
modality);
mPendingAuthSession.mState = STATE_AUTH_CALLED;
// No polymorphism :(
if ((modality & TYPE_FINGERPRINT) != 0) {
mFingerprintService.prepareForAuthentication(token, sessionId, userId,
mInternalReceiver, opPackageName, cookie,
callingUid, callingPid, callingUserId);
}
if ((modality & TYPE_IRIS) != 0) {
Slog.w(TAG, "Iris unsupported");
}
if ((modality & TYPE_FACE) != 0) {
mFaceService.prepareForAuthentication(true /* requireConfirmation */,
token, sessionId, userId, mInternalReceiver, opPackageName,
cookie, callingUid, callingPid, callingUserId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to start authentication", e);
}
}
@Override // Binder call
public void cancelAuthentication(IBinder token, String opPackageName)
throws RemoteException {
checkPermission();
if (token == null || opPackageName == null) {
Slog.e(TAG, "Unable to cancel, one or more null arguments");
return;
}
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
// We need to check the current authenticators state. If we're pending confirm
// or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client,
// since we won't be getting an onError from the driver.
if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
mHandler.post(() -> {
try {
// Send error to client
mCurrentAuthSession.mClientReceiver.onError(
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
getContext().getString(
com.android.internal.R.string.biometric_error_user_canceled)
);
mHandler.post(() -> {
try {
if (mCurrentModality == BIOMETRIC_FINGERPRINT) {
mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
callingUid, callingPid, callingUserId);
} else if (mCurrentModality == BIOMETRIC_IRIS) {
Slog.w(TAG, "Unsupported modality");
} else if (mCurrentModality == BIOMETRIC_FACE) {
mFaceService.cancelAuthenticationFromService(token, opPackageName,
callingUid, callingPid, callingUserId);
} else {
Slog.w(TAG, "Unsupported modality");
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
mStatusBarService.hideBiometricDialog();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to cancel authentication");
}
});
});
} else {
cancelInternal(token, opPackageName, true /* fromClient */);
}
}
@Override // Binder call
@@ -402,6 +812,31 @@ public class BiometricService extends SystemService {
Binder.restoreCallingIdentity(ident);
}
}
void cancelInternal(IBinder token, String opPackageName, boolean fromClient) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
mHandler.post(() -> {
try {
// TODO: For multiple modalities, send a single ERROR_CANCELED only when all
// drivers have canceled authentication.
if ((mCurrentModality & TYPE_FINGERPRINT) != 0) {
mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
callingUid, callingPid, callingUserId, fromClient);
}
if ((mCurrentModality & TYPE_IRIS) != 0) {
Slog.w(TAG, "Iris unsupported");
}
if ((mCurrentModality & TYPE_FACE) != 0) {
mFaceService.cancelAuthenticationFromService(token, opPackageName,
callingUid, callingPid, callingUserId, fromClient);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to cancel authentication");
}
});
}
}
private void checkAppOp(String opPackageName, int callingUid) {
@@ -413,7 +848,7 @@ public class BiometricService extends SystemService {
}
private void checkInternalPermission() {
getContext().enforceCallingPermission(USE_BIOMETRIC_INTERNAL,
getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL,
"Must have USE_BIOMETRIC_INTERNAL permission");
}
@@ -490,16 +925,19 @@ public class BiometricService extends SystemService {
* returns errors through the callback (no biometric feature, hardware not detected, no
* templates enrolled, etc). This service must not start authentication if errors are sent.
*
* @Returns A pair [Modality, Error] with Modality being one of {@link #BIOMETRIC_NONE},
* {@link #BIOMETRIC_FINGERPRINT}, {@link #BIOMETRIC_IRIS}, {@link #BIOMETRIC_FACE}
* @Returns A pair [Modality, Error] with Modality being one of
* {@link BiometricAuthenticator#TYPE_NONE},
* {@link BiometricAuthenticator#TYPE_FINGERPRINT},
* {@link BiometricAuthenticator#TYPE_IRIS},
* {@link BiometricAuthenticator#TYPE_FACE}
* and the error containing one of the {@link BiometricConstants} errors.
*/
private Pair<Integer, Integer> checkAndGetBiometricModality(int callingUid) {
int modality = BIOMETRIC_NONE;
int modality = TYPE_NONE;
// No biometric features, send error
if (mAuthenticators.isEmpty()) {
return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
}
// Assuming that authenticators are listed in priority-order, the rest of this function
@@ -512,13 +950,13 @@ public class BiometricService extends SystemService {
boolean hasTemplatesEnrolled = false;
boolean enabledForApps = false;
int firstHwAvailable = BIOMETRIC_NONE;
int firstHwAvailable = TYPE_NONE;
for (int i = 0; i < mAuthenticators.size(); i++) {
modality = mAuthenticators.get(i).getType();
BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator();
if (authenticator.isHardwareDetected()) {
isHardwareDetected = true;
if (firstHwAvailable == BIOMETRIC_NONE) {
if (firstHwAvailable == TYPE_NONE) {
// Store the first one since we want to return the error in correct priority
// order.
firstHwAvailable = modality;
@@ -538,13 +976,13 @@ public class BiometricService extends SystemService {
// Check error conditions
if (!isHardwareDetected) {
return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
} else if (!hasTemplatesEnrolled) {
// Return the modality here so the correct error string can be sent. This error is
// preferred over !enabledForApps
return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
} else if (!enabledForApps) {
return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
}
return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS);
@@ -552,11 +990,11 @@ public class BiometricService extends SystemService {
private boolean isEnabledForApp(int modality) {
switch(modality) {
case BIOMETRIC_FINGERPRINT:
case TYPE_FINGERPRINT:
return true;
case BIOMETRIC_IRIS:
case TYPE_IRIS:
return true;
case BIOMETRIC_FACE:
case TYPE_FACE:
return mSettingObserver.getFaceEnabledForApps();
default:
Slog.w(TAG, "Unsupported modality: " + modality);
@@ -566,12 +1004,12 @@ public class BiometricService extends SystemService {
private String getErrorString(int type, int error, int vendorCode) {
switch (type) {
case BIOMETRIC_FINGERPRINT:
case TYPE_FINGERPRINT:
return FingerprintManager.getErrorString(getContext(), error, vendorCode);
case BIOMETRIC_IRIS:
case TYPE_IRIS:
Slog.w(TAG, "Modality not supported");
return null; // not supported
case BIOMETRIC_FACE:
case TYPE_FACE:
return FaceManager.getErrorString(getContext(), error, vendorCode);
default:
Slog.w(TAG, "Unable to get error string for modality: " + type);
@@ -581,12 +1019,12 @@ public class BiometricService extends SystemService {
private BiometricAuthenticator getAuthenticator(int type) {
switch (type) {
case BIOMETRIC_FINGERPRINT:
case TYPE_FINGERPRINT:
return (FingerprintManager)
getContext().getSystemService(Context.FINGERPRINT_SERVICE);
case BIOMETRIC_IRIS:
case TYPE_IRIS:
return null;
case BIOMETRIC_FACE:
case TYPE_FACE:
return (FaceManager)
getContext().getSystemService(Context.FACE_SERVICE);
default:
@@ -596,11 +1034,11 @@ public class BiometricService extends SystemService {
private boolean hasFeature(int type) {
switch (type) {
case BIOMETRIC_FINGERPRINT:
case TYPE_FINGERPRINT:
return mHasFeatureFingerprint;
case BIOMETRIC_IRIS:
case TYPE_IRIS:
return mHasFeatureIris;
case BIOMETRIC_FACE:
case TYPE_FACE:
return mHasFeatureFace;
default:
return false;

View File

@@ -16,7 +16,6 @@
package com.android.server.biometrics;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import android.app.ActivityManager;
@@ -36,8 +35,9 @@ import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.fingerprint.Fingerprint;
import android.os.Binder;
import android.os.Bundle;
@@ -56,7 +56,6 @@ import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.SystemService;
@@ -106,6 +105,7 @@ public abstract class BiometricServiceBase extends SystemService
protected final AppOpsManager mAppOps;
protected final H mHandler = new H();
private IBiometricService mBiometricService;
private ClientMonitor mCurrentClient;
private ClientMonitor mPendingClient;
private PerformanceStats mPerformanceStats;
@@ -223,12 +223,9 @@ public abstract class BiometricServiceBase extends SystemService
public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId,
boolean restricted, String owner, Bundle bundle,
IBiometricPromptReceiver dialogReceiver,
IStatusBarService statusBarService, boolean requireConfirmation) {
super(context, getMetrics(), daemon, halDeviceId, token, listener,
targetUserId, groupId, opId, restricted, owner, bundle, dialogReceiver,
statusBarService, requireConfirmation);
boolean restricted, String owner, int cookie, boolean requireConfirmation) {
super(context, getMetrics(), daemon, halDeviceId, token, listener, targetUserId,
groupId, opId, restricted, owner, cookie, requireConfirmation);
}
@Override
@@ -279,11 +276,6 @@ public abstract class BiometricServiceBase extends SystemService
}
return AuthenticationClient.LOCKOUT_NONE;
}
@Override
public void onAuthenticationConfirmed() {
removeClient(mCurrentClient);
}
}
protected class EnrollClientImpl extends EnrollClient {
@@ -345,18 +337,28 @@ public abstract class BiometricServiceBase extends SystemService
default void onEnrollResult(BiometricAuthenticator.Identifier identifier,
int remaining) throws RemoteException {};
void onAcquired(long deviceId, int acquiredInfo, int vendorCode)
throws RemoteException;
void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException;
void onAuthenticationSucceeded(long deviceId,
BiometricAuthenticator.Identifier biometric, int userId)
throws RemoteException;
default void onAuthenticationSucceeded(long deviceId,
BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
throw new UnsupportedOperationException("Stub!");
}
void onAuthenticationFailed(long deviceId)
throws RemoteException;
default void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
throws RemoteException {
throw new UnsupportedOperationException("Stub!");
}
void onError(long deviceId, int error, int vendorCode)
throws RemoteException;
default void onAuthenticationFailed(long deviceId) throws RemoteException {
throw new UnsupportedOperationException("Stub!");
}
default void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
throws RemoteException {
throw new UnsupportedOperationException("Stub!");
}
void onError(long deviceId, int error, int vendorCode, int cookie) throws RemoteException;
default void onRemoved(BiometricAuthenticator.Identifier identifier,
int remaining) throws RemoteException {};
@@ -365,6 +367,37 @@ public abstract class BiometricServiceBase extends SystemService
int remaining) throws RemoteException {};
}
/**
* Wraps the callback interface from Service -> BiometricPrompt
*/
protected abstract class BiometricServiceListener implements ServiceListener {
private IBiometricServiceReceiverInternal mWrapperReceiver;
public BiometricServiceListener(IBiometricServiceReceiverInternal wrapperReceiver) {
mWrapperReceiver = wrapperReceiver;
}
public IBiometricServiceReceiverInternal getWrapperReceiver() {
return mWrapperReceiver;
}
@Override
public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token);
}
}
@Override
public void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation);
}
}
}
/**
* Wraps a portion of the interface from Service -> Daemon that is used by the ClientMonitor
* subclasses.
@@ -706,30 +739,6 @@ public abstract class BiometricServiceBase extends SystemService
}
mHandler.post(() -> {
if (client.isBiometricPrompt()) {
try {
final List<ActivityManager.RunningAppProcessInfo> procs =
ActivityManager.getService().getRunningAppProcesses();
for (int i = 0; i < procs.size(); i++) {
final ActivityManager.RunningAppProcessInfo info = procs.get(i);
if (info.uid == callingUid && info.importance == IMPORTANCE_FOREGROUND) {
PackageManager pm = getContext().getPackageManager();
final CharSequence label = pm.getApplicationLabel(
pm.getApplicationInfo(info.processName,
PackageManager.GET_META_DATA));
final String title = getContext()
.getString(R.string.biometric_dialog_default_title, label);
client.setTitleIfEmpty(title);
break;
}
}
} catch (RemoteException e) {
Slog.e(getTag(), "Unable to get application name", e);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(getTag(), "Unable to get application name", e);
}
}
mMetricsLogger.histogram(getMetrics().tagAuthToken(), opId != 0L ? 1 : 0);
// Get performance stats object for this user.
@@ -751,29 +760,37 @@ public abstract class BiometricServiceBase extends SystemService
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId);
cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId,
true /* fromClient */);
}
protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName,
int callingUid, int callingPid, int callingUserId) {
if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
callingUserId)) {
if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
return;
int callingUid, int callingPid, int callingUserId, boolean fromClient) {
if (fromClient) {
// Only check this if cancel was called from the client (app). If cancel was called
// from BiometricService, it means the dialog was dismissed due to user interaction.
if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
callingUserId)) {
if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
return;
}
}
mHandler.post(() -> {
ClientMonitor client = mCurrentClient;
if (client instanceof AuthenticationClient) {
if (client.getToken() == token) {
if (DEBUG) Slog.v(getTag(), "stop client " + client.getOwnerString());
if (client.getToken() == token || !fromClient) {
if (DEBUG) Slog.v(getTag(), "Stopping client " + client.getOwnerString()
+ ", fromClient: " + fromClient);
// If cancel was from BiometricService, it means the dialog was dismissed
// and authentication should be canceled.
client.stop(client.getToken() == token);
} else {
if (DEBUG) Slog.v(getTag(), "can't stop client "
+ client.getOwnerString() + " since tokens don't match");
if (DEBUG) Slog.v(getTag(), "Can't stop client " + client.getOwnerString()
+ " since tokens don't match. fromClient: " + fromClient);
}
} else if (client != null) {
if (DEBUG) Slog.v(getTag(), "can't cancel non-authenticating client "
if (DEBUG) Slog.v(getTag(), "Can't cancel non-authenticating client "
+ client.getOwnerString());
}
});
@@ -805,8 +822,7 @@ public abstract class BiometricServiceBase extends SystemService
int lockoutMode = getLockoutMode();
if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
Slog.v(getTag(), "In lockout mode(" + lockoutMode +
") ; disallowing authentication");
Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication");
int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
@@ -919,7 +935,6 @@ public abstract class BiometricServiceBase extends SystemService
if (currentClient != null) {
if (DEBUG) Slog.v(getTag(), "request stop current client " +
currentClient.getOwnerString());
// This check only matters for FingerprintService, since enumerate may call back
// multiple times.
if (currentClient instanceof FingerprintService.EnumerateClientImpl ||
@@ -940,17 +955,51 @@ public abstract class BiometricServiceBase extends SystemService
mHandler.removeCallbacks(mResetClientState);
mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
} else if (newClient != null) {
mCurrentClient = newClient;
if (DEBUG) Slog.v(getTag(), "starting client "
+ newClient.getClass().getSuperclass().getSimpleName()
+ "(" + newClient.getOwnerString() + ")"
+ ", initiatedByClient = " + initiatedByClient);
notifyClientActiveCallbacks(true);
// For BiometricPrompt clients, do not start until
// <Biometric>Service#startPreparedClient is called. BiometricService waits until all
// modalities are ready before initiating authentication.
if (newClient instanceof AuthenticationClient) {
AuthenticationClient client = (AuthenticationClient) newClient;
if (client.isBiometricPrompt()) {
if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie());
mCurrentClient = newClient;
if (mBiometricService == null) {
mBiometricService = IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE));
}
try {
mBiometricService.onReadyForAuthentication(client.getCookie(),
client.getRequireConfirmation(), client.getTargetUserId());
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception", e);
}
return;
}
}
newClient.start();
// We are not a BiometricPrompt client, start the client immediately
mCurrentClient = newClient;
startCurrentClient(mCurrentClient.getCookie());
}
}
protected void startCurrentClient(int cookie) {
if (mCurrentClient == null) {
Slog.e(getTag(), "Trying to start null client!");
return;
}
if (DEBUG) Slog.v(getTag(), "starting client "
+ mCurrentClient.getClass().getSuperclass().getSimpleName()
+ "(" + mCurrentClient.getOwnerString() + ")"
+ " cookie: " + cookie + "/" + mCurrentClient.getCookie());
if (cookie != mCurrentClient.getCookie()) {
Slog.e(getTag(), "Mismatched cookie");
return;
}
notifyClientActiveCallbacks(true);
mCurrentClient.start();
}
protected void removeClient(ClientMonitor client) {
if (client != null) {
client.destroy();

View File

@@ -58,6 +58,9 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient {
private IBinder mToken;
private BiometricServiceBase.ServiceListener mListener;
// Currently only used for authentication client. The cookie generated by BiometricService
// is never 0.
private final int mCookie;
protected final MetricsLogger mMetricsLogger;
protected final Metrics mMetrics;
@@ -80,7 +83,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient {
public ClientMonitor(Context context, Metrics metrics,
BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
BiometricServiceBase.ServiceListener listener, int userId, int groupId,
boolean restricted, String owner) {
boolean restricted, String owner, int cookie) {
mContext = context;
mMetrics = metrics;
mDaemon = daemon;
@@ -91,6 +94,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient {
mGroupId = groupId;
mIsRestricted = restricted;
mOwner = owner;
mCookie = cookie;
mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
mMetricsLogger = new MetricsLogger();
@@ -107,6 +111,10 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient {
return mMetrics.logTag();
}
public int getCookie() {
return mCookie;
}
/**
* Contacts the biometric's HAL to start the client.
* @return 0 on success, errno from driver on failure
@@ -174,7 +182,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient {
public boolean onError(long deviceId, int error, int vendorCode) {
try {
if (mListener != null) {
mListener.onError(deviceId, error, vendorCode);
mListener.onError(deviceId, error, vendorCode, getCookie());
}
} catch (RemoteException e) {
Slog.w(getLogTag(), "Failed to invoke sendError", e);

View File

@@ -40,7 +40,7 @@ public abstract class EnrollClient extends ClientMonitor {
BiometricServiceBase.ServiceListener listener, int userId, int groupId,
byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils) {
super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
owner);
owner, 0 /* cookie */);
mBiometricUtils = utils;
mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
}

View File

@@ -34,7 +34,7 @@ public abstract class EnumerateClient extends ClientMonitor {
BiometricServiceBase.ServiceListener listener, int groupId, int userId,
boolean restricted, String owner) {
super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
owner);
owner, 0 /* cookie */);
}
@Override

View File

@@ -37,7 +37,7 @@ public abstract class RemovalClient extends ClientMonitor {
BiometricServiceBase.ServiceListener listener, int biometricId, int groupId, int userId,
boolean restricted, String owner, BiometricUtils utils) {
super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
owner);
owner, 0 /* cookie */);
mBiometricId = biometricId;
mBiometricUtils = utils;
}

View File

@@ -27,9 +27,8 @@ import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
import android.hardware.biometrics.face.V1_0.Status;
@@ -38,7 +37,6 @@ import android.hardware.face.FaceManager;
import android.hardware.face.IFaceService;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
@@ -50,7 +48,6 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.biometrics.BiometricServiceBase;
@@ -89,27 +86,9 @@ public class FaceService extends BiometricServiceBase {
public FaceAuthClient(Context context,
DaemonWrapper daemon, long halDeviceId, IBinder token,
ServiceListener listener, int targetUserId, int groupId, long opId,
boolean restricted, String owner, Bundle bundle,
IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService,
boolean requireConfirmation) {
boolean restricted, String owner, int cookie, boolean requireConfirmation) {
super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
restricted, owner, bundle, dialogReceiver, statusBarService,
requireConfirmation);
}
@Override
public String getErrorString(int error, int vendorCode) {
return FaceManager.getErrorString(getContext(), error, vendorCode);
}
@Override
public String getAcquiredString(int acquireInfo, int vendorCode) {
return FaceManager.getAcquiredString(getContext(), acquireInfo, vendorCode);
}
@Override
public int getBiometricType() {
return BiometricAuthenticator.TYPE_FACE;
restricted, owner, cookie, requireConfirmation);
}
}
@@ -162,27 +141,32 @@ public class FaceService extends BiometricServiceBase {
final AuthenticationClientImpl client = new FaceAuthClient(getContext(),
mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,
null /* bundle */, null /* dialogReceiver */, mStatusBarService,
false /* requireConfirmation */);
0 /* cookie */, false /* requireConfirmation */);
authenticateInternal(client, opId, opPackageName);
}
@Override // Binder call
public void authenticateFromService(boolean requireConfirmation, IBinder token, long opId,
int groupId, IBiometricServiceReceiver receiver, int flags,
String opPackageName, Bundle bundle, IBiometricPromptReceiver dialogReceiver,
int callingUid, int callingPid, int callingUserId) {
public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long opId,
int groupId, IBiometricServiceReceiverInternal wrapperReceiver,
String opPackageName, int cookie, int callingUid, int callingPid,
int callingUserId) {
checkPermission(USE_BIOMETRIC_INTERNAL);
final boolean restricted = true; // BiometricPrompt is always restricted
final AuthenticationClientImpl client = new FaceAuthClient(getContext(),
mDaemonWrapper, mHalDeviceId, token,
new BiometricPromptServiceListenerImpl(receiver),
mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,
bundle, dialogReceiver, mStatusBarService, true /* requireConfirmation */);
new BiometricPromptServiceListenerImpl(wrapperReceiver),
mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie,
true /* requireConfirmation */);
authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
callingUserId);
}
@Override // Binder call
public void startPreparedClient(int cookie) {
checkPermission(MANAGE_BIOMETRIC);
startCurrentClient(cookie);
}
@Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName) {
checkPermission(USE_BIOMETRIC_INTERNAL);
@@ -191,10 +175,10 @@ public class FaceService extends BiometricServiceBase {
@Override // Binder call
public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
int callingUid, int callingPid, int callingUserId) {
int callingUid, int callingPid, int callingUserId, boolean fromClient) {
checkPermission(USE_BIOMETRIC_INTERNAL);
cancelAuthenticationInternal(token, opPackageName,
callingUid, callingPid, callingUserId);
cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid,
callingUserId, fromClient);
}
@Override // Binder call
@@ -405,12 +389,9 @@ public class FaceService extends BiometricServiceBase {
* Receives callbacks from the ClientMonitor implementations. The results are forwarded to
* BiometricPrompt.
*/
private class BiometricPromptServiceListenerImpl implements ServiceListener {
private IBiometricServiceReceiver mBiometricServiceReceiver;
public BiometricPromptServiceListenerImpl(IBiometricServiceReceiver receiver) {
mBiometricServiceReceiver = receiver;
private class BiometricPromptServiceListenerImpl extends BiometricServiceListener {
BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) {
super(wrapperReceiver);
}
@Override
@@ -419,32 +400,18 @@ public class FaceService extends BiometricServiceBase {
/**
* Map the acquired codes onto existing {@link BiometricConstants} acquired codes.
*/
if (mBiometricServiceReceiver != null) {
mBiometricServiceReceiver.onAcquired(deviceId,
if (getWrapperReceiver() != null) {
getWrapperReceiver().onAcquired(
FaceManager.getMappedAcquiredInfo(acquiredInfo, vendorCode),
FaceManager.getAcquiredString(getContext(), acquiredInfo, vendorCode));
}
}
@Override
public void onAuthenticationSucceeded(long deviceId,
BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
if (mBiometricServiceReceiver != null) {
mBiometricServiceReceiver.onAuthenticationSucceeded(deviceId);
}
}
@Override
public void onAuthenticationFailed(long deviceId) throws RemoteException {
if (mBiometricServiceReceiver != null) {
mBiometricServiceReceiver.onAuthenticationFailed(deviceId);
}
}
@Override
public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
if (mBiometricServiceReceiver != null) {
mBiometricServiceReceiver.onError(deviceId, error,
public void onError(long deviceId, int error, int vendorCode, int cookie)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onError(cookie, error,
FaceManager.getErrorString(getContext(), error, vendorCode));
}
}
@@ -455,7 +422,6 @@ public class FaceService extends BiometricServiceBase {
* the FaceManager.
*/
private class ServiceListenerImpl implements ServiceListener {
private IFaceServiceReceiver mFaceServiceReceiver;
public ServiceListenerImpl(IFaceServiceReceiver receiver) {
@@ -501,7 +467,8 @@ public class FaceService extends BiometricServiceBase {
}
@Override
public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
public void onError(long deviceId, int error, int vendorCode, int cookie)
throws RemoteException {
if (mFaceServiceReceiver != null) {
mFaceServiceReceiver.onError(deviceId, error, vendorCode);
}

View File

@@ -30,9 +30,8 @@ import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
@@ -42,7 +41,6 @@ import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
@@ -55,7 +53,6 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.biometrics.AuthenticationClient;
@@ -109,27 +106,10 @@ public class FingerprintService extends BiometricServiceBase {
public FingerprintAuthClient(Context context,
DaemonWrapper daemon, long halDeviceId, IBinder token,
ServiceListener listener, int targetUserId, int groupId, long opId,
boolean restricted, String owner, Bundle bundle,
IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService,
boolean restricted, String owner, int cookie,
boolean requireConfirmation) {
super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
restricted, owner, bundle, dialogReceiver, statusBarService,
requireConfirmation);
}
@Override
public String getErrorString(int error, int vendorCode) {
return FingerprintManager.getErrorString(getContext(), error, vendorCode);
}
@Override
public String getAcquiredString(int acquireInfo, int vendorCode) {
return FingerprintManager.getAcquiredString(getContext(), acquireInfo, vendorCode);
}
@Override
public int getBiometricType() {
return BiometricAuthenticator.TYPE_FINGERPRINT;
restricted, owner, cookie, requireConfirmation);
}
}
@@ -182,27 +162,33 @@ public class FingerprintService extends BiometricServiceBase {
final boolean restricted = isRestricted();
final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
mCurrentUserId, groupId, opId, restricted, opPackageName, null /* bundle */,
null /* dialogReceiver */, mStatusBarService, false /* requireConfirmation */);
mCurrentUserId, groupId, opId, restricted, opPackageName,
0 /* cookie */, false /* requireConfirmation */);
authenticateInternal(client, opId, opPackageName);
}
@Override // Binder call
public void authenticateFromService(IBinder token, long opId, int groupId,
IBiometricServiceReceiver receiver, int flags, String opPackageName,
Bundle bundle, IBiometricPromptReceiver dialogReceiver,
int callingUid, int callingPid, int callingUserId) {
public void prepareForAuthentication(IBinder token, long opId, int groupId,
IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
int cookie, int callingUid, int callingPid, int callingUserId) {
checkPermission(MANAGE_BIOMETRIC);
final boolean restricted = true; // BiometricPrompt is always restricted
final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
mDaemonWrapper, mHalDeviceId, token,
new BiometricPromptServiceListenerImpl(receiver),
mCurrentUserId, groupId, opId, restricted, opPackageName, bundle,
dialogReceiver, mStatusBarService, false /* requireConfirmation */);
new BiometricPromptServiceListenerImpl(wrapperReceiver),
mCurrentUserId, groupId, opId, restricted, opPackageName, cookie,
false /* requireConfirmation */);
authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
callingUserId);
}
@Override // Binder call
public void startPreparedClient(int cookie) {
checkPermission(MANAGE_BIOMETRIC);
startCurrentClient(cookie);
}
@Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName) {
cancelAuthenticationInternal(token, opPackageName);
@@ -210,10 +196,10 @@ public class FingerprintService extends BiometricServiceBase {
@Override // Binder call
public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
int callingUid, int callingPid, int callingUserId) {
int callingUid, int callingPid, int callingUserId, boolean fromClient) {
checkPermission(MANAGE_BIOMETRIC);
cancelAuthenticationInternal(token, opPackageName,
callingUid, callingPid, callingUserId);
cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid,
callingUserId, fromClient);
}
@Override // Binder call
@@ -388,43 +374,25 @@ public class FingerprintService extends BiometricServiceBase {
* Receives callbacks from the ClientMonitor implementations. The results are forwarded to
* BiometricPrompt.
*/
private class BiometricPromptServiceListenerImpl implements ServiceListener {
private IBiometricServiceReceiver mBiometricServiceReceiver;
public BiometricPromptServiceListenerImpl(IBiometricServiceReceiver receiver) {
mBiometricServiceReceiver = receiver;
private class BiometricPromptServiceListenerImpl extends BiometricServiceListener {
BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) {
super(wrapperReceiver);
}
@Override
public void onAcquired(long deviceId, int acquiredInfo, int vendorCode)
throws RemoteException {
if (mBiometricServiceReceiver != null) {
mBiometricServiceReceiver.onAcquired(deviceId, acquiredInfo,
FingerprintManager.getAcquiredString(
if (getWrapperReceiver() != null) {
getWrapperReceiver().onAcquired(acquiredInfo, FingerprintManager.getAcquiredString(
getContext(), acquiredInfo, vendorCode));
}
}
@Override
public void onAuthenticationSucceeded(long deviceId,
BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
if (mBiometricServiceReceiver != null) {
mBiometricServiceReceiver.onAuthenticationSucceeded(deviceId);
}
}
@Override
public void onAuthenticationFailed(long deviceId) throws RemoteException {
if (mBiometricServiceReceiver != null) {
mBiometricServiceReceiver.onAuthenticationFailed(deviceId);
}
}
@Override
public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
if (mBiometricServiceReceiver != null) {
mBiometricServiceReceiver.onError(deviceId, error,
public void onError(long deviceId, int error, int vendorCode, int cookie)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onError(cookie, error,
FingerprintManager.getErrorString(getContext(), error, vendorCode));
}
}
@@ -435,7 +403,6 @@ public class FingerprintService extends BiometricServiceBase {
* the FingerprintManager.
*/
private class ServiceListenerImpl implements ServiceListener {
private IFingerprintServiceReceiver mFingerprintServiceReceiver;
public ServiceListenerImpl(IFingerprintServiceReceiver receiver) {
@@ -483,7 +450,8 @@ public class FingerprintService extends BiometricServiceBase {
}
@Override
public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
public void onError(long deviceId, int error, int vendorCode, int cookie)
throws RemoteException {
if (mFingerprintServiceReceiver != null) {
mFingerprintServiceReceiver.onError(deviceId, error, vendorCode);
}

View File

@@ -24,7 +24,7 @@ import android.app.StatusBarManager;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -598,8 +598,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
}
@Override
public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type,
boolean requireConfirmation, int userId) {
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) {
enforceBiometricDialog();
if (mBar != null) {
try {
@@ -653,6 +653,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
}
}
@Override
public void showBiometricTryAgain() {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.showBiometricTryAgain();
} catch (RemoteException ex) {
}
}
}
@Override
public void disable(int what, IBinder token, String pkg) {
disableForUser(what, token, pkg, mCurrentUserId);