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:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(() -> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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] -->
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -43,4 +43,9 @@ public interface DialogViewCallback {
|
||||
* should be dismissed.
|
||||
*/
|
||||
void onPositivePressed();
|
||||
|
||||
/**
|
||||
* Invoked when the "try again" button is pressed.
|
||||
*/
|
||||
void onTryAgainPressed();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user