3/n: For passive modalities, add plumbing for "try again"

When "try again" is showing, authentication is canceled internally.
BiometricService caches the client's info so that authentication can
be restarted when "try again" is pressed. Because authentication
is not running when "try again" is showing, BiometricService also needs
to have a TaskStackListener so that BP can be dismissed and an error can
be sent to the client when the app loses focus.

IBiometricServiceReceiver has been split into two. One for BiometricPrompt
to receive messages from BiometricService, and another for BiometricService
to receive messages from SystemUI/<Biometric>Services.

When we get locked out, don't send the last onAuthenticationFailed
to the client, since "Authentication failed" will be shown briefly
and be replaced by "Device locked out" which is janky

Bug: 111461540

Test: Tested with requireConfirmation enabled/disabled
Test: Tested onConfigurationChange corner cases, e.g. when "try again"
      or "confirm" buttons are showing, rotate the device. Buttons
      persist correctly and don't appear when unexpected
Test: Tested task stack corner cases, e.g. when "try again" is showing,
      press home button. BP dismisses and client receives ERROR_CANCELED
Test: BiometricPromptDemo receives all callbacks

Change-Id: I62126708ce8db8b358c666a07aa7c39607642c9d
This commit is contained in:
Kevin Chyn
2018-11-28 16:32:36 -08:00
parent 87f257a9e5
commit 23289ef7b6
22 changed files with 510 additions and 178 deletions

View File

@@ -159,6 +159,7 @@ java_defaults {
"core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl",
"core/java/android/hardware/biometrics/IBiometricService.aidl",
"core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl",
"core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl",
"core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl",
"core/java/android/hardware/display/IDisplayManager.aidl",
"core/java/android/hardware/display/IDisplayManagerCallback.aidl",

View File

@@ -262,12 +262,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
});
}
@Override
public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] bytes)
throws RemoteException {
throw new UnsupportedOperationException("Operation not supported!");
}
@Override
public void onAuthenticationFailed() throws RemoteException {
mExecutor.execute(() -> {
@@ -282,11 +276,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
});
}
@Override
public void onErrorInternal(int error, String message, int cookie) throws RemoteException {
throw new UnsupportedOperationException("Operation not supported!");
}
@Override
public void onAcquired(int acquireInfo, String message) throws RemoteException {
mExecutor.execute(() -> {

View File

@@ -16,28 +16,16 @@
package android.hardware.biometrics;
/**
* Communication channel from
* 1) BiometricDialogImpl (SysUI) back to BiometricService
* 2) <Biometric>Service back to BiometricService
* 3) BiometricService back to BiometricPrompt
* BiometricPrompt sends a receiver to BiometricService, BiometricService contains another
* "trampoline" receiver which intercepts messages from <Biometric>Service and does some
* logic before forwarding results as necessary to BiometricPrompt.
* Communication channel from BiometricService back to BiometricPrompt
* @hide
*/
oneway interface IBiometricServiceReceiver {
// Notify BiometricPrompt that authentication was successful
void onAuthenticationSucceeded();
// Notify BiometricService that authentication was successful. If user confirmation is required,
// the auth token must be submitted into KeyStore.
void onAuthenticationSucceededInternal(boolean requireConfirmation, in byte[] token);
// Noties that authentication failed.
void onAuthenticationFailed();
// Notify BiometricPrompt that an error has occurred.
void onError(int error, String message);
// Notify BiometricService than an error has occured. Forward to the correct receiver depending
// on the cookie.
void onErrorInternal(int error, String message, int cookie);
// Notifies that a biometric has been acquired.
void onAcquired(int acquiredInfo, String message);
// Notifies that the SystemUI dialog has been dismissed.

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.biometrics;
/**
* Communication channel from
* 1) BiometricDialogImpl (SysUI) back to BiometricService
* 2) <Biometric>Service back to BiometricService
* Receives messages from the above and does some handling before forwarding to BiometricPrompt
* via IBiometricServiceReceiver.
* @hide
*/
oneway interface IBiometricServiceReceiverInternal {
// Notify BiometricService that authentication was successful. If user confirmation is required,
// the auth token must be submitted into KeyStore.
void onAuthenticationSucceeded(boolean requireConfirmation, in byte[] token);
// Notify BiometricService that an error has occurred.
void onAuthenticationFailed(int cookie, boolean requireConfirmation);
// Notify BiometricService than an error has occured. Forward to the correct receiver depending
// on the cookie.
void onError(int cookie, int error, String message);
// Notifies that a biometric has been acquired.
void onAcquired(int acquiredInfo, String message);
// Notifies that the SystemUI dialog has been dismissed.
void onDialogDismissed(int reason);
// Notifies that the user has pressed the "try again" button on SystemUI
void onTryAgainPressed();
}

View File

@@ -15,7 +15,7 @@
*/
package android.hardware.face;
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;
@@ -36,7 +36,7 @@ interface IFaceService {
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
void prepareForAuthentication(boolean requireConfirmation, IBinder token, long sessionId,
int userId, IBiometricServiceReceiver wrapperReceiver, String opPackageName,
int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
int cookie, int callingUid, int callingPid, int callingUserId);
// Starts authentication with the previously prepared client.

View File

@@ -15,7 +15,7 @@
*/
package android.hardware.fingerprint;
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;
@@ -39,7 +39,7 @@ interface IFingerprintService {
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
void prepareForAuthentication(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver wrapperReceiver, String opPackageName, int cookie,
IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, int cookie,
int callingUid, int callingPid, int callingUserId);
// Starts authentication with the previously prepared client.

View File

@@ -18,7 +18,7 @@ package com.android.internal.statusbar;
import android.content.ComponentName;
import android.graphics.Rect;
import android.hardware.biometrics.IBiometricServiceReceiver;
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, IBiometricServiceReceiver receiver, int type,
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
void onBiometricAuthenticated();
@@ -151,4 +151,6 @@ oneway interface IStatusBar
void onBiometricError(String error);
// Used to hide the biometric dialog when the AuthenticationClient is stopped
void hideBiometricDialog();
// Used to request the "try again" button for authentications which requireConfirmation=true
void showBiometricTryAgain();
}

View File

@@ -20,7 +20,7 @@ import android.content.ComponentName;
import android.graphics.Rect;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
@@ -91,7 +91,7 @@ interface IStatusBarService
void showPinningEscapeToast();
// Used to show the dialog when BiometricService starts authentication
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiver 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();
@@ -101,4 +101,6 @@ interface IStatusBarService
void onBiometricError(String error);
// Used to hide the biometric dialog when the AuthenticationClient is stopped
void hideBiometricDialog();
// Used to request the "try again" button for authentications which requireConfirmation=true
void showBiometricTryAgain();
}

View File

@@ -1444,11 +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>
@@ -1464,8 +1467,6 @@
<string-array name="fingerprint_acquired_vendor">
</string-array>
<!-- Message shown by the biometric dialog when biometric is not recognized -->
<string name="biometric_not_recognized">Not recognized</string>
<!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] -->
<string name="fingerprint_authenticated">Fingerprint authenticated</string>
<!-- Accessibility message announced when a face has been authenticated [CHAR LIMIT=NONE] -->

View File

@@ -2404,6 +2404,7 @@
<java-symbol type="string" name="biometric_error_hw_unavailable" />
<java-symbol type="string" name="biometric_error_user_canceled" />
<java-symbol type="string" name="biometric_not_recognized" />
<java-symbol type="string" name="biometric_error_canceled" />
<!-- Fingerprint messages -->
<java-symbol type="string" name="fingerprint_error_unable_to_process" />

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiver;
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 IBiometricServiceReceiver 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,8 +158,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
}
@Override
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiver 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);
@@ -180,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;
@@ -194,11 +219,13 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
Log.w(TAG, "Dialog already showing");
return;
}
mReceiver = (IBiometricServiceReceiver) 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;
}
@@ -210,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 */);
}
@@ -227,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);
}
@@ -247,6 +276,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
}
mReceiver = null;
mDialogShowing = false;
mConfirmShowing = false;
mTryAgainShowing = false;
mCurrentDialog.startDismiss();
}
@@ -260,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 */);
}
@@ -273,13 +305,31 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling positive button", e);
}
mConfirmShowing = false;
handleHideDialog(false /* userCanceled */);
}
private void handleUserCanceled() {
mTryAgainShowing = false;
mConfirmShowing = false;
handleHideDialog(true /* userCanceled */);
}
private void handleShowTryAgain() {
mCurrentDialog.showTryAgainButton(true /* show */);
mTryAgainShowing = true;
}
private void handleTryAgainPressed() {
try {
mCurrentDialog.clearTemporaryMessage();
mTryAgainShowing = false;
mReceiver.onTryAgainPressed();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when handling try again", e);
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);

View File

@@ -87,6 +87,9 @@ public abstract class BiometricDialogView extends LinearLayout {
protected boolean mRequireConfirmation;
private int mUserId; // used to determine if we should show work background
private boolean mPendingShowTryAgain;
private boolean mPendingShowConfirm;
protected abstract void updateIcon(int lastState, int newState);
protected abstract int getHintStringResourceId();
protected abstract int getAuthenticatedAccessibilityResourceId();
@@ -178,6 +181,7 @@ public abstract class BiometricDialogView extends LinearLayout {
final Button negative = mLayout.findViewById(R.id.button2);
final Button positive = mLayout.findViewById(R.id.button1);
final ImageView icon = mLayout.findViewById(R.id.biometric_icon);
final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
icon.setContentDescription(getResources().getString(getIconDescriptionResourceId()));
@@ -193,6 +197,11 @@ public abstract class BiometricDialogView extends LinearLayout {
mCallback.onPositivePressed();
});
tryAgain.setOnClickListener((View v) -> {
showTryAgainButton(false /* show */);
mCallback.onTryAgainPressed();
});
mLayout.setFocusableInTouchMode(true);
mLayout.requestFocus();
}
@@ -207,7 +216,6 @@ public abstract class BiometricDialogView extends LinearLayout {
final TextView subtitle = mLayout.findViewById(R.id.subtitle);
final TextView description = mLayout.findViewById(R.id.description);
final Button negative = mLayout.findViewById(R.id.button2);
final Button positive = mLayout.findViewById(R.id.button1);
final ImageView backgroundView = mLayout.findViewById(R.id.background);
if (mUserManager.isManagedProfile(mUserId)) {
@@ -233,8 +241,6 @@ public abstract class BiometricDialogView extends LinearLayout {
title.setText(titleText);
title.setSelected(true);
positive.setVisibility(View.INVISIBLE);
final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
if (TextUtils.isEmpty(subtitleText)) {
subtitle.setVisibility(View.GONE);
@@ -243,7 +249,8 @@ public abstract class BiometricDialogView extends LinearLayout {
subtitle.setText(subtitleText);
}
final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
final CharSequence descriptionText =
mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
if (TextUtils.isEmpty(descriptionText)) {
description.setVisibility(View.GONE);
} else {
@@ -253,6 +260,9 @@ public abstract class BiometricDialogView extends LinearLayout {
negative.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
showTryAgainButton(mPendingShowTryAgain);
showConfirmationButton(mPendingShowConfirm);
if (mWasForceRemoved || mSkipIntro) {
// Show the dialog immediately
mLayout.animate().cancel();
@@ -281,6 +291,7 @@ 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() {
@@ -288,6 +299,9 @@ public abstract class BiometricDialogView extends LinearLayout {
mAnimatingAway = false;
// Set the icons / text back to normal state
handleClearMessage();
showTryAgainButton(false /* show */);
mPendingShowTryAgain = false;
mPendingShowConfirm = false;
}
};
@@ -347,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) {
@@ -378,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();
}
@@ -392,6 +416,25 @@ public abstract class BiometricDialogView extends LinearLayout {
mLastState = newState;
}
public void showTryAgainButton(boolean show) {
final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
if (show) {
tryAgain.setVisibility(View.VISIBLE);
} else {
tryAgain.setVisibility(View.GONE);
}
}
// Set the state before the window is attached, so we know if the dialog should be started
// with or without the button. This is because there's no good onPause signal
public void setPendingTryAgain(boolean show) {
mPendingShowTryAgain = show;
}
public void setPendingConfirm(boolean show) {
mPendingShowConfirm = show;
}
public WindowManager.LayoutParams getLayoutParams() {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,

View File

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

View File

@@ -21,7 +21,7 @@ import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS;
import android.app.StatusBarManager;
import android.content.ComponentName;
import android.graphics.Rect;
import android.hardware.biometrics.IBiometricServiceReceiver;
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, IBiometricServiceReceiver 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, IBiometricServiceReceiver 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,
(IBiometricServiceReceiver) someArgs.arg2,
(IBiometricServiceReceiverInternal) someArgs.arg2,
someArgs.argi1 /* type */,
(boolean) someArgs.arg3 /* requireConfirmation */,
someArgs.argi2 /* userId */);
@@ -816,6 +825,11 @@ public class CommandQueue extends IStatusBar.Stub {
mCallbacks.get(i).showPinningEscapeToast();
}
break;
case MSG_BIOMETRIC_TRY_AGAIN:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showBiometricTryAgain();
}
break;
}
}
}

View File

@@ -131,10 +131,8 @@ public abstract class AuthenticationClient extends ClientMonitor {
}
} else {
if (listener != null) {
listener.onAuthenticationFailed(getHalDeviceId());
vibrateError();
}
// Allow system-defined limit of number of attempts before giving up
final int lockoutMode = handleFailedAttempt();
if (lockoutMode != LOCKOUT_NONE) {
@@ -148,6 +146,18 @@ public abstract class AuthenticationClient extends ClientMonitor {
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());
}
}
}
result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
}

View File

@@ -26,7 +26,10 @@ 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;
@@ -39,6 +42,7 @@ import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
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;
@@ -245,29 +249,67 @@ public class BiometricService extends SystemService {
* 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;
class AuthSession {
// Original receiver from BiometricPrompt.
final IBiometricServiceReceiver mClientReceiver;
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, Bundle bundle,
IBiometricServiceReceiver receiver) {
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;
mBundle = bundle;
mToken = token;
mSessionId = sessionId;
mUserId = userId;
mClientReceiver = receiver;
mOpPackageName = opPackageName;
mBundle = bundle;
mCallingUid = callingUid;
mCallingPid = callingPid;
mCallingUserId = callingUserId;
mModality = modality;
}
boolean containsCookie(int cookie) {
@@ -281,29 +323,58 @@ public class BiometricService extends SystemService {
}
}
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 IBiometricServiceReceiver mWrapperReceiver =
new IBiometricServiceReceiver.Stub() {
private final IBiometricServiceReceiverInternal mInternalReceiver =
new IBiometricServiceReceiverInternal.Stub() {
@Override
public void onAuthenticationSucceeded() throws RemoteException {
throw new UnsupportedOperationException("Operation not supported");
}
@Override
public void onAuthenticationSucceededInternal(boolean requireConfirmation,
byte[] token) throws RemoteException {
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;
@@ -312,6 +383,7 @@ public class BiometricService extends SystemService {
// 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
@@ -323,10 +395,20 @@ public class BiometricService extends SystemService {
}
@Override
public void onAuthenticationFailed() throws RemoteException {
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);
@@ -334,13 +416,7 @@ public class BiometricService extends SystemService {
}
@Override
public void onError(int error, String message) throws RemoteException {
throw new UnsupportedOperationException("Operation not supported!");
}
@Override
public void onErrorInternal(int error, String message, int cookie)
throws RemoteException {
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
@@ -349,15 +425,15 @@ public class BiometricService extends SystemService {
// to be started. Thus we must match error messages with their cookies to be sure
// of their intended receivers.
try {
mStatusBarService.onBiometricError(message);
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();
mCurrentAuthSession.mClientReceiver.onError(error, message);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
mStatusBarService.hideBiometricDialog();
} else {
// Send errors after the dialog is dismissed.
mHandler.postDelayed(() -> {
@@ -370,6 +446,24 @@ public class BiometricService extends SystemService {
}
}, 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);
@@ -422,9 +516,29 @@ public class BiometricService extends SystemService {
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
@@ -444,6 +558,8 @@ public class BiometricService extends SystemService {
}
if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) {
final boolean mContinuing = mCurrentAuthSession != null
&& mCurrentAuthSession.mState == STATE_AUTH_PAUSED;
mCurrentAuthSession = mPendingAuthSession;
mPendingAuthSession = null;
@@ -465,8 +581,11 @@ public class BiometricService extends SystemService {
modality |= pair.getKey();
}
mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
mWrapperReceiver, modality, requireConfirmation, userId);
if (!mContinuing) {
mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
mInternalReceiver, modality, requireConfirmation, userId);
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -475,8 +594,8 @@ public class BiometricService extends SystemService {
@Override // Binder call
public void authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName,
Bundle bundle) 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();
@@ -503,6 +622,34 @@ public class BiometricService extends SystemService {
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(() -> {
@@ -536,80 +683,91 @@ public class BiometricService extends SystemService {
return;
}
// 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);
}
mCurrentModality = modality;
// Actually start authentication
mCurrentModality = 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: " + mCurrentModality
+ ", cookie: " + cookie);
HashMap<Integer, Integer> authenticators = new HashMap<>();
authenticators.put(mCurrentModality, cookie);
mPendingAuthSession = new AuthSession(authenticators, bundle, receiver);
mPendingAuthSession.mState = STATE_AUTH_CALLED;
// No polymorphism :(
if ((mCurrentModality & TYPE_FINGERPRINT) != 0) {
mFingerprintService.prepareForAuthentication(token, sessionId, userId,
mWrapperReceiver, opPackageName, cookie,
callingUid, callingPid, callingUserId);
}
if ((mCurrentModality & TYPE_IRIS) != 0) {
Slog.w(TAG, "Iris unsupported");
}
if ((mCurrentModality & TYPE_FACE) != 0) {
mFaceService.prepareForAuthentication(true /* requireConfirmation */,
token, sessionId, userId, mWrapperReceiver, opPackageName,
cookie, callingUid, callingPid, callingUserId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to start authentication", e);
}
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;
}
cancelInternal(token, opPackageName, true /* fromClient */);
// 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)
);
mCurrentAuthSession.mState = STATE_AUTH_IDLE;
mCurrentAuthSession = null;
mStatusBarService.hideBiometricDialog();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
});
} else {
cancelInternal(token, opPackageName, true /* fromClient */);
}
}
@Override // Binder call

View File

@@ -37,7 +37,7 @@ import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.fingerprint.Fingerprint;
import android.os.Binder;
import android.os.Bundle;
@@ -349,8 +349,14 @@ public abstract class BiometricServiceBase extends SystemService
throw new UnsupportedOperationException("Stub!");
}
void onAuthenticationFailed(long deviceId)
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;
@@ -365,14 +371,13 @@ public abstract class BiometricServiceBase extends SystemService
* Wraps the callback interface from Service -> BiometricPrompt
*/
protected abstract class BiometricServiceListener implements ServiceListener {
// We should send results using the wrapper receiver.
private IBiometricServiceReceiver mWrapperReceiver;
private IBiometricServiceReceiverInternal mWrapperReceiver;
public BiometricServiceListener(IBiometricServiceReceiver wrapperReceiver) {
public BiometricServiceListener(IBiometricServiceReceiverInternal wrapperReceiver) {
mWrapperReceiver = wrapperReceiver;
}
public IBiometricServiceReceiver getWrapperReceiver() {
public IBiometricServiceReceiverInternal getWrapperReceiver() {
return mWrapperReceiver;
}
@@ -380,14 +385,15 @@ public abstract class BiometricServiceBase extends SystemService
public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onAuthenticationSucceededInternal(requireConfirmation, token);
getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token);
}
}
@Override
public void onAuthenticationFailed(long deviceId) throws RemoteException {
public void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onAuthenticationFailed();
getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation);
}
}
}

View File

@@ -28,7 +28,7 @@ import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
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;
@@ -147,8 +147,9 @@ public class FaceService extends BiometricServiceBase {
@Override // Binder call
public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long opId,
int groupId, IBiometricServiceReceiver wrapperReceiver, String opPackageName,
int cookie, int callingUid, int callingPid, int callingUserId) {
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(),
@@ -389,7 +390,7 @@ public class FaceService extends BiometricServiceBase {
* BiometricPrompt.
*/
private class BiometricPromptServiceListenerImpl extends BiometricServiceListener {
BiometricPromptServiceListenerImpl(IBiometricServiceReceiver wrapperReceiver) {
BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) {
super(wrapperReceiver);
}
@@ -410,9 +411,8 @@ public class FaceService extends BiometricServiceBase {
public void onError(long deviceId, int error, int vendorCode, int cookie)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onErrorInternal(error,
FaceManager.getErrorString(getContext(), error, vendorCode),
cookie);
getWrapperReceiver().onError(cookie, error,
FaceManager.getErrorString(getContext(), error, vendorCode));
}
}
}

View File

@@ -31,7 +31,7 @@ import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricServiceReceiver;
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;
@@ -169,7 +169,7 @@ public class FingerprintService extends BiometricServiceBase {
@Override // Binder call
public void prepareForAuthentication(IBinder token, long opId, int groupId,
IBiometricServiceReceiver wrapperReceiver, String opPackageName,
IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
int cookie, int callingUid, int callingPid, int callingUserId) {
checkPermission(MANAGE_BIOMETRIC);
final boolean restricted = true; // BiometricPrompt is always restricted
@@ -375,7 +375,7 @@ public class FingerprintService extends BiometricServiceBase {
* BiometricPrompt.
*/
private class BiometricPromptServiceListenerImpl extends BiometricServiceListener {
BiometricPromptServiceListenerImpl(IBiometricServiceReceiver wrapperReceiver) {
BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) {
super(wrapperReceiver);
}
@@ -392,9 +392,8 @@ public class FingerprintService extends BiometricServiceBase {
public void onError(long deviceId, int error, int vendorCode, int cookie)
throws RemoteException {
if (getWrapperReceiver() != null) {
getWrapperReceiver().onErrorInternal(error,
FingerprintManager.getErrorString(getContext(), error, vendorCode),
cookie);
getWrapperReceiver().onError(cookie, error,
FingerprintManager.getErrorString(getContext(), error, vendorCode));
}
}
}

View File

@@ -23,7 +23,7 @@ import android.app.StatusBarManager;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -567,8 +567,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
}
@Override
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiver 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 {
@@ -622,6 +622,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);