Merge changes from topic "biometric-service-migration"
* changes: Remove strings from low level onError(...) calls Prepare BiometricService for migration to a module
This commit is contained in:
committed by
Android (Google) Code Review
commit
f4ded6d022
@@ -36,23 +36,30 @@ public interface BiometricAuthenticator {
|
||||
* @hide
|
||||
*/
|
||||
int TYPE_NONE = 0;
|
||||
|
||||
/**
|
||||
* Constant representing credential (PIN, pattern, or password).
|
||||
* @hide
|
||||
*/
|
||||
int TYPE_CREDENTIAL = 1 << 0;
|
||||
|
||||
/**
|
||||
* Constant representing fingerprint.
|
||||
* @hide
|
||||
*/
|
||||
int TYPE_FINGERPRINT = 1 << 0;
|
||||
int TYPE_FINGERPRINT = 1 << 1;
|
||||
|
||||
/**
|
||||
* Constant representing iris.
|
||||
* @hide
|
||||
*/
|
||||
int TYPE_IRIS = 1 << 1;
|
||||
int TYPE_IRIS = 1 << 2;
|
||||
|
||||
/**
|
||||
* Constant representing face.
|
||||
* @hide
|
||||
*/
|
||||
int TYPE_FACE = 1 << 2;
|
||||
int TYPE_FACE = 1 << 3;
|
||||
|
||||
/**
|
||||
* Container for biometric data
|
||||
|
||||
@@ -133,6 +133,13 @@ public interface BiometricConstants {
|
||||
*/
|
||||
int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
|
||||
|
||||
/**
|
||||
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
|
||||
* because the authentication attempt was unsuccessful.
|
||||
* @hide
|
||||
*/
|
||||
int BIOMETRIC_PAUSED_REJECTED = 100;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
|
||||
@@ -137,7 +137,7 @@ public class BiometricManager {
|
||||
public boolean hasEnrolledBiometrics(int userId) {
|
||||
if (mService != null) {
|
||||
try {
|
||||
return mService.hasEnrolledBiometrics(userId);
|
||||
return mService.hasEnrolledBiometrics(userId, mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Remote exception in hasEnrolledBiometrics(): " + e);
|
||||
return false;
|
||||
|
||||
@@ -26,6 +26,8 @@ import android.annotation.RequiresPermission;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
@@ -339,9 +341,23 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int error, String message) throws RemoteException {
|
||||
public void onError(int modality, int error, int vendorCode) throws RemoteException {
|
||||
mExecutor.execute(() -> {
|
||||
mAuthenticationCallback.onAuthenticationError(error, message);
|
||||
String errorMessage;
|
||||
switch (modality) {
|
||||
case TYPE_FACE:
|
||||
errorMessage = FaceManager.getErrorString(mContext, error, vendorCode);
|
||||
break;
|
||||
|
||||
case TYPE_FINGERPRINT:
|
||||
errorMessage = FingerprintManager.getErrorString(mContext, error,
|
||||
vendorCode);
|
||||
break;
|
||||
|
||||
default:
|
||||
errorMessage = "";
|
||||
}
|
||||
mAuthenticationCallback.onAuthenticationError(error, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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;
|
||||
|
||||
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
|
||||
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
|
||||
import android.hardware.face.IFaceServiceReceiver;
|
||||
import android.hardware.face.Face;
|
||||
|
||||
/**
|
||||
* This interface encapsulates fingerprint, face, iris, etc. authenticators.
|
||||
* Implementations of this interface are meant to be registered with BiometricService.
|
||||
* @hide
|
||||
*/
|
||||
interface IBiometricAuthenticator {
|
||||
|
||||
// This method prepares the service to start authenticating, but doesn't start authentication.
|
||||
// This is protected by the MANAGE_BIOMETRIC signature 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);
|
||||
|
||||
// Same as above, with extra arguments.
|
||||
void cancelAuthenticationFromService(IBinder token, String opPackageName,
|
||||
int callingUid, int callingPid, int callingUserId, boolean fromClient);
|
||||
|
||||
// Determine if HAL is loaded and ready
|
||||
boolean isHardwareDetected(String opPackageName);
|
||||
|
||||
// Determine if a user has at least one enrolled face
|
||||
boolean hasEnrolledTemplates(int userId, String opPackageName);
|
||||
|
||||
// Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
|
||||
void resetLockout(in byte [] token);
|
||||
|
||||
// Explicitly set the active user (for enrolling work profile)
|
||||
void setActiveUser(int uid);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package android.hardware.biometrics;
|
||||
import android.os.Bundle;
|
||||
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
|
||||
import android.hardware.biometrics.IBiometricServiceReceiver;
|
||||
import android.hardware.biometrics.IBiometricAuthenticator;
|
||||
|
||||
/**
|
||||
* Communication channel from BiometricPrompt and BiometricManager to BiometricService. The
|
||||
@@ -40,7 +41,12 @@ interface IBiometricService {
|
||||
int canAuthenticate(String opPackageName, int userId);
|
||||
|
||||
// Checks if any biometrics are enrolled.
|
||||
boolean hasEnrolledBiometrics(int userId);
|
||||
boolean hasEnrolledBiometrics(int userId, String opPackageName);
|
||||
|
||||
// Registers an authenticator (e.g. face, fingerprint, iris).
|
||||
// Id must be unique, whereas strength and modality don't need to be.
|
||||
// TODO(b/123321528): Turn strength and modality into enums.
|
||||
void registerAuthenticator(int id, int strength, int modality, IBiometricAuthenticator authenticator);
|
||||
|
||||
// Register callback for when keyguard biometric eligibility changes.
|
||||
void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
|
||||
|
||||
@@ -25,7 +25,7 @@ oneway interface IBiometricServiceReceiver {
|
||||
// Noties that authentication failed.
|
||||
void onAuthenticationFailed();
|
||||
// Notify BiometricPrompt that an error has occurred.
|
||||
void onError(int error, String message);
|
||||
void onError(int modality, int error, int vendorCode);
|
||||
// Notifies that a biometric has been acquired.
|
||||
void onAcquired(int acquiredInfo, String message);
|
||||
// Notifies that the SystemUI dialog has been dismissed.
|
||||
|
||||
@@ -31,7 +31,7 @@ oneway interface IBiometricServiceReceiverInternal {
|
||||
void onAuthenticationFailed();
|
||||
// Notify BiometricService than an error has occured. Forward to the correct receiver depending
|
||||
// on the cookie.
|
||||
void onError(int cookie, int error, String message);
|
||||
void onError(int cookie, int modality, int error, int vendorCode);
|
||||
// Notifies that a biometric has been acquired.
|
||||
void onAcquired(int acquiredInfo, String message);
|
||||
// Notifies that the SystemUI dialog has been dismissed.
|
||||
|
||||
@@ -504,8 +504,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
|
||||
public boolean isHardwareDetected() {
|
||||
if (mService != null) {
|
||||
try {
|
||||
long deviceId = 0; /* TODO: plumb hardware id to FPMS */
|
||||
return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
|
||||
return mService.isHardwareDetected(mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ interface IFaceService {
|
||||
List<Face> getEnrolledFaces(int userId, String opPackageName);
|
||||
|
||||
// Determine if HAL is loaded and ready
|
||||
boolean isHardwareDetected(long deviceId, String opPackageName);
|
||||
boolean isHardwareDetected(String opPackageName);
|
||||
|
||||
// Get a pre-enrollment authentication token
|
||||
long generateChallenge(IBinder token);
|
||||
|
||||
@@ -691,8 +691,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
|
||||
public boolean isHardwareDetected() {
|
||||
if (mService != null) {
|
||||
try {
|
||||
long deviceId = 0; /* TODO: plumb hardware id to FPMS */
|
||||
return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
|
||||
return mService.isHardwareDetected(mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ interface IFingerprintService {
|
||||
List<Fingerprint> getEnrolledFingerprints(int groupId, String opPackageName);
|
||||
|
||||
// Determine if HAL is loaded and ready
|
||||
boolean isHardwareDetected(long deviceId, String opPackageName);
|
||||
boolean isHardwareDetected(String opPackageName);
|
||||
|
||||
// Get a pre-enrollment authentication token
|
||||
long preEnroll(IBinder token);
|
||||
|
||||
@@ -155,12 +155,12 @@ oneway interface IStatusBar
|
||||
// Used to show the authentication dialog (Biometrics, Device Credential)
|
||||
void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver,
|
||||
int biometricModality, boolean requireConfirmation, int userId, String opPackageName);
|
||||
// Used to notify the authentication dialog that a biometric has been authenticated or rejected
|
||||
void onBiometricAuthenticated(boolean authenticated, String failureReason);
|
||||
// Used to notify the authentication dialog that a biometric has been authenticated
|
||||
void onBiometricAuthenticated();
|
||||
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
|
||||
void onBiometricHelp(String message);
|
||||
// Used to set a message - the dialog will dismiss after a certain amount of time
|
||||
void onBiometricError(int errorCode, String error);
|
||||
// Used to show an error - the dialog will dismiss after a certain amount of time
|
||||
void onBiometricError(int modality, int error, int vendorCode);
|
||||
// Used to hide the authentication dialog, e.g. when the application cancels authentication
|
||||
void hideAuthenticationDialog();
|
||||
|
||||
|
||||
@@ -104,12 +104,12 @@ interface IStatusBarService
|
||||
// Used to show the authentication dialog (Biometrics, Device Credential)
|
||||
void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver,
|
||||
int biometricModality, boolean requireConfirmation, int userId, String opPackageName);
|
||||
// Used to notify the authentication dialog that a biometric has been authenticated or rejected
|
||||
void onBiometricAuthenticated(boolean authenticated, String failureReason);
|
||||
// Used to notify the authentication dialog that a biometric has been authenticated
|
||||
void onBiometricAuthenticated();
|
||||
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
|
||||
void onBiometricHelp(String message);
|
||||
// Used to set a message - the dialog will dismiss after a certain amount of time
|
||||
void onBiometricError(int errorCode, String error);
|
||||
// Used to show an error - the dialog will dismiss after a certain amount of time
|
||||
void onBiometricError(int modality, int error, int vendorCode);
|
||||
// Used to hide the authentication dialog, e.g. when the application cancels authentication
|
||||
void hideAuthenticationDialog();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.systemui.biometrics;
|
||||
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.IActivityTaskManager;
|
||||
@@ -27,6 +30,8 @@ import android.hardware.biometrics.Authenticator;
|
||||
import android.hardware.biometrics.BiometricConstants;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@@ -34,6 +39,7 @@ import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.systemui.SystemUI;
|
||||
@@ -229,15 +235,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
|
||||
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
|
||||
+ " reason: " + failureReason);
|
||||
|
||||
if (authenticated) {
|
||||
mCurrentDialog.onAuthenticationSucceeded();
|
||||
} else {
|
||||
mCurrentDialog.onAuthenticationFailed(failureReason);
|
||||
}
|
||||
public void onBiometricAuthenticated() {
|
||||
mCurrentDialog.onAuthenticationSucceeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,16 +246,45 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
|
||||
mCurrentDialog.onHelp(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBiometricError(int errorCode, String error) {
|
||||
if (DEBUG) Log.d(TAG, "onBiometricError: " + errorCode + ", " + error);
|
||||
private String getErrorString(int modality, int error, int vendorCode) {
|
||||
switch (modality) {
|
||||
case TYPE_FACE:
|
||||
return FaceManager.getErrorString(mContext, error, vendorCode);
|
||||
|
||||
case TYPE_FINGERPRINT:
|
||||
return FingerprintManager.getErrorString(mContext, error, vendorCode);
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBiometricError(int modality, int error, int vendorCode) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
|
||||
}
|
||||
|
||||
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|
||||
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
|
||||
|
||||
// TODO(b/141025588): Create separate methods for handling hard and soft errors.
|
||||
final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
|
||||
|| error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
|
||||
|
||||
final boolean isLockout = errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
|
||||
|| errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
|
||||
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
|
||||
if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
|
||||
mCurrentDialog.animateToCredentialUI();
|
||||
} else if (isSoftError) {
|
||||
final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
|
||||
? mContext.getString(R.string.biometric_not_recognized)
|
||||
: getErrorString(modality, error, vendorCode);
|
||||
if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
|
||||
mCurrentDialog.onAuthenticationFailed(errorMessage);
|
||||
} else {
|
||||
mCurrentDialog.onError(error);
|
||||
final String errorMessage = getErrorString(modality, error, vendorCode);
|
||||
if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
|
||||
mCurrentDialog.onError(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -261,9 +261,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
|
||||
default void showAuthenticationDialog(Bundle bundle,
|
||||
IBiometricServiceReceiverInternal receiver, int biometricModality,
|
||||
boolean requireConfirmation, int userId, String opPackageName) { }
|
||||
default void onBiometricAuthenticated(boolean authenticated, String failureReason) { }
|
||||
default void onBiometricAuthenticated() { }
|
||||
default void onBiometricHelp(String message) { }
|
||||
default void onBiometricError(int errorCode, String error) { }
|
||||
default void onBiometricError(int modality, int error, int vendorCode) { }
|
||||
default void hideAuthenticationDialog() { }
|
||||
|
||||
/**
|
||||
@@ -792,12 +792,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
|
||||
public void onBiometricAuthenticated() {
|
||||
synchronized (mLock) {
|
||||
SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = authenticated;
|
||||
args.arg2 = failureReason;
|
||||
mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
|
||||
mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,9 +806,13 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBiometricError(int errorCode, String error) {
|
||||
public void onBiometricError(int modality, int error, int vendorCode) {
|
||||
synchronized (mLock) {
|
||||
mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, errorCode, 0, error).sendToTarget();
|
||||
SomeArgs args = SomeArgs.obtain();
|
||||
args.argi1 = modality;
|
||||
args.argi2 = error;
|
||||
args.argi3 = vendorCode;
|
||||
mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, args).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1098,13 +1099,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
|
||||
break;
|
||||
}
|
||||
case MSG_BIOMETRIC_AUTHENTICATED: {
|
||||
SomeArgs someArgs = (SomeArgs) msg.obj;
|
||||
for (int i = 0; i < mCallbacks.size(); i++) {
|
||||
mCallbacks.get(i).onBiometricAuthenticated(
|
||||
(boolean) someArgs.arg1 /* authenticated */,
|
||||
(String) someArgs.arg2 /* failureReason */);
|
||||
mCallbacks.get(i).onBiometricAuthenticated();
|
||||
}
|
||||
someArgs.recycle();
|
||||
break;
|
||||
}
|
||||
case MSG_BIOMETRIC_HELP:
|
||||
@@ -1113,9 +1110,15 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
|
||||
}
|
||||
break;
|
||||
case MSG_BIOMETRIC_ERROR:
|
||||
SomeArgs someArgs = (SomeArgs) msg.obj;
|
||||
for (int i = 0; i < mCallbacks.size(); i++) {
|
||||
mCallbacks.get(i).onBiometricError(msg.arg1, (String) msg.obj);
|
||||
mCallbacks.get(i).onBiometricError(
|
||||
someArgs.argi1 /* modality */,
|
||||
someArgs.argi2 /* error */,
|
||||
someArgs.argi3 /* vendorCode */
|
||||
);
|
||||
}
|
||||
someArgs.recycle();
|
||||
break;
|
||||
case MSG_BIOMETRIC_HIDE:
|
||||
for (int i = 0; i < mCallbacks.size(); i++) {
|
||||
|
||||
@@ -38,15 +38,18 @@ import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.biometrics.Authenticator;
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.hardware.biometrics.BiometricConstants;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.os.Bundle;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableContext;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.statusbar.CommandQueue;
|
||||
import com.android.systemui.statusbar.phone.StatusBar;
|
||||
@@ -89,9 +92,9 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
|
||||
when(context.getPackageManager()).thenReturn(mPackageManager);
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
|
||||
.thenReturn(true);
|
||||
.thenReturn(true);
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
|
||||
.thenReturn(true);
|
||||
.thenReturn(true);
|
||||
|
||||
when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
|
||||
when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
|
||||
@@ -170,20 +173,34 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
@Test
|
||||
public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
|
||||
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
|
||||
mAuthController.onBiometricAuthenticated(true, null /* failureReason */);
|
||||
mAuthController.onBiometricAuthenticated();
|
||||
verify(mDialog1).onAuthenticationSucceeded();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnAuthenticationFailedInvoked_whenSystemRequested() {
|
||||
public void testOnAuthenticationFailedInvoked_whenBiometricRejected() {
|
||||
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
|
||||
final String failureReason = "failure reason";
|
||||
mAuthController.onBiometricAuthenticated(false, failureReason);
|
||||
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_NONE,
|
||||
BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
|
||||
0 /* vendorCode */);
|
||||
|
||||
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mDialog1).onAuthenticationFailed(captor.capture());
|
||||
|
||||
assertEquals(captor.getValue(), failureReason);
|
||||
assertEquals(captor.getValue(), mContext.getString(R.string.biometric_not_recognized));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
|
||||
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
|
||||
final int error = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
|
||||
final int vendorCode = 0;
|
||||
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
|
||||
|
||||
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mDialog1).onAuthenticationFailed(captor.capture());
|
||||
|
||||
assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -199,27 +216,27 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnErrorInvoked_whenSystemRequested() {
|
||||
public void testOnErrorInvoked_whenSystemRequested() throws Exception {
|
||||
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
|
||||
final int error = 1;
|
||||
final String errMessage = "error message";
|
||||
mAuthController.onBiometricError(error, errMessage);
|
||||
final int vendorCode = 0;
|
||||
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
|
||||
|
||||
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mDialog1).onError(captor.capture());
|
||||
|
||||
assertEquals(captor.getValue(), errMessage);
|
||||
assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() {
|
||||
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
|
||||
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
|
||||
final String errorString = "lockout";
|
||||
final int vendorCode = 0;
|
||||
|
||||
when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
|
||||
|
||||
mAuthController.onBiometricError(error, errorString);
|
||||
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
|
||||
verify(mDialog1, never()).onError(anyString());
|
||||
verify(mDialog1).animateToCredentialUI();
|
||||
}
|
||||
@@ -228,11 +245,11 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI() {
|
||||
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
|
||||
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
|
||||
final String errorString = "lockout_permanent";
|
||||
final int vendorCode = 0;
|
||||
|
||||
when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
|
||||
|
||||
mAuthController.onBiometricError(error, errorString);
|
||||
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
|
||||
verify(mDialog1, never()).onError(anyString());
|
||||
verify(mDialog1).animateToCredentialUI();
|
||||
}
|
||||
@@ -241,12 +258,12 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
|
||||
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
|
||||
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
|
||||
final String errorString = "lockout";
|
||||
final int vendorCode = 0;
|
||||
|
||||
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
|
||||
|
||||
mAuthController.onBiometricError(error, errorString);
|
||||
verify(mDialog1).onError(eq(errorString));
|
||||
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
|
||||
verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
|
||||
verify(mDialog1, never()).animateToCredentialUI();
|
||||
}
|
||||
|
||||
@@ -254,12 +271,12 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
|
||||
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
|
||||
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
|
||||
final String errorString = "lockout_permanent";
|
||||
final int vendorCode = 0;
|
||||
|
||||
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
|
||||
|
||||
mAuthController.onBiometricError(error, errorString);
|
||||
verify(mDialog1).onError(eq(errorString));
|
||||
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
|
||||
verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
|
||||
verify(mDialog1, never()).animateToCredentialUI();
|
||||
}
|
||||
|
||||
|
||||
@@ -418,10 +418,9 @@ public class CommandQueueTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testOnBiometricAuthenticated() {
|
||||
String failureReason = "test_failure_reason";
|
||||
mCommandQueue.onBiometricAuthenticated(true /* authenticated */, failureReason);
|
||||
mCommandQueue.onBiometricAuthenticated();
|
||||
waitForIdleSync();
|
||||
verify(mCallbacks).onBiometricAuthenticated(eq(true), eq(failureReason));
|
||||
verify(mCallbacks).onBiometricAuthenticated();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -434,11 +433,12 @@ public class CommandQueueTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testOnBiometricError() {
|
||||
final int errorCode = 1;
|
||||
String errorMessage = "test_error_message";
|
||||
mCommandQueue.onBiometricError(errorCode, errorMessage);
|
||||
final int modality = 1;
|
||||
final int error = 2;
|
||||
final int vendorCode = 3;
|
||||
mCommandQueue.onBiometricError(modality, error, vendorCode);
|
||||
waitForIdleSync();
|
||||
verify(mCallbacks).onBiometricError(eq(errorCode), eq(errorMessage));
|
||||
verify(mCallbacks).onBiometricError(eq(modality), eq(error), eq(vendorCode));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -37,13 +37,12 @@ import android.hardware.biometrics.BiometricConstants;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.hardware.biometrics.BiometricSourceType;
|
||||
import android.hardware.biometrics.BiometricsProtoEnums;
|
||||
import android.hardware.biometrics.IBiometricAuthenticator;
|
||||
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;
|
||||
import android.hardware.fingerprint.IFingerprintService;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
@@ -68,6 +67,8 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.biometrics.face.FaceAuthenticator;
|
||||
import com.android.server.biometrics.fingerprint.FingerprintAuthenticator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -95,11 +96,6 @@ public class BiometricService extends SystemService {
|
||||
private static final int MSG_CANCEL_AUTHENTICATION = 10;
|
||||
private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11;
|
||||
private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
|
||||
private static final int[] FEATURE_ID = {
|
||||
TYPE_FINGERPRINT,
|
||||
TYPE_IRIS,
|
||||
TYPE_FACE
|
||||
};
|
||||
|
||||
/**
|
||||
* Authentication either just called and we have not transitioned to the CALLED state, or
|
||||
@@ -172,7 +168,7 @@ public class BiometricService extends SystemService {
|
||||
byte[] mTokenEscrow;
|
||||
// Waiting for SystemUI to complete animation
|
||||
int mErrorEscrow;
|
||||
String mErrorStringEscrow;
|
||||
int mVendorCodeEscrow;
|
||||
|
||||
// Timestamp when authentication started
|
||||
private long mStartTimeMs;
|
||||
@@ -219,18 +215,14 @@ public class BiometricService extends SystemService {
|
||||
private final Injector mInjector;
|
||||
@VisibleForTesting
|
||||
final IBiometricService.Stub mImpl;
|
||||
private final boolean mHasFeatureFace;
|
||||
private final boolean mHasFeatureFingerprint;
|
||||
private final boolean mHasFeatureIris;
|
||||
private final boolean mHasFeatureFace;
|
||||
@VisibleForTesting
|
||||
final SettingObserver mSettingObserver;
|
||||
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
|
||||
private final Random mRandom = new Random();
|
||||
|
||||
@VisibleForTesting
|
||||
IFingerprintService mFingerprintService;
|
||||
@VisibleForTesting
|
||||
IFaceService mFaceService;
|
||||
@VisibleForTesting
|
||||
IStatusBarService mStatusBarService;
|
||||
@VisibleForTesting
|
||||
@@ -262,7 +254,7 @@ public class BiometricService extends SystemService {
|
||||
}
|
||||
|
||||
case MSG_ON_AUTHENTICATION_REJECTED: {
|
||||
handleAuthenticationRejected((String) msg.obj /* failureReason */);
|
||||
handleAuthenticationRejected();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -270,8 +262,9 @@ public class BiometricService extends SystemService {
|
||||
SomeArgs args = (SomeArgs) msg.obj;
|
||||
handleOnError(
|
||||
args.argi1 /* cookie */,
|
||||
args.argi2 /* error */,
|
||||
(String) args.arg1 /* message */);
|
||||
args.argi2 /* modality */,
|
||||
args.argi3 /* error */,
|
||||
args.argi4 /* vendorCode */);
|
||||
args.recycle();
|
||||
break;
|
||||
}
|
||||
@@ -331,7 +324,12 @@ public class BiometricService extends SystemService {
|
||||
}
|
||||
|
||||
case MSG_ON_AUTHENTICATION_TIMED_OUT: {
|
||||
handleAuthenticationTimedOut((String) msg.obj /* errorMessage */);
|
||||
SomeArgs args = (SomeArgs) msg.obj;
|
||||
handleAuthenticationTimedOut(
|
||||
args.argi1 /* modality */,
|
||||
args.argi2 /* error */,
|
||||
args.argi3 /* vendorCode */);
|
||||
args.recycle();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -347,21 +345,23 @@ public class BiometricService extends SystemService {
|
||||
}
|
||||
};
|
||||
|
||||
private final class AuthenticatorWrapper {
|
||||
final int mType;
|
||||
final BiometricAuthenticator mAuthenticator;
|
||||
/**
|
||||
* Wraps IBiometricAuthenticator implementation and stores information about the authenticator.
|
||||
* TODO(b/141025588): Consider refactoring the tests to not rely on this implementation detail.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final class AuthenticatorWrapper {
|
||||
public final int id;
|
||||
public final int strength;
|
||||
public final int modality;
|
||||
public final IBiometricAuthenticator impl;
|
||||
|
||||
AuthenticatorWrapper(int type, BiometricAuthenticator authenticator) {
|
||||
mType = type;
|
||||
mAuthenticator = authenticator;
|
||||
}
|
||||
|
||||
int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
BiometricAuthenticator getAuthenticator() {
|
||||
return mAuthenticator;
|
||||
AuthenticatorWrapper(int id, int strength, int modality,
|
||||
IBiometricAuthenticator impl) {
|
||||
this.id = id;
|
||||
this.strength = strength;
|
||||
this.modality = modality;
|
||||
this.impl = impl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,23 +521,28 @@ public class BiometricService extends SystemService {
|
||||
@Override
|
||||
public void onAuthenticationFailed()
|
||||
throws RemoteException {
|
||||
String failureReason = getContext().getString(R.string.biometric_not_recognized);
|
||||
Slog.v(TAG, "onAuthenticationFailed: " + failureReason);
|
||||
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED, failureReason).sendToTarget();
|
||||
Slog.v(TAG, "onAuthenticationFailed");
|
||||
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int cookie, int error, String message) throws RemoteException {
|
||||
public void onError(int cookie, int modality, int error, int vendorCode)
|
||||
throws RemoteException {
|
||||
// Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
|
||||
// soft errors and we should allow the user to try authenticating again instead of
|
||||
// dismissing BiometricPrompt.
|
||||
if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
|
||||
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, message).sendToTarget();
|
||||
SomeArgs args = SomeArgs.obtain();
|
||||
args.argi1 = modality;
|
||||
args.argi2 = error;
|
||||
args.argi3 = vendorCode;
|
||||
mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, args).sendToTarget();
|
||||
} else {
|
||||
SomeArgs args = SomeArgs.obtain();
|
||||
args.argi1 = cookie;
|
||||
args.argi2 = error;
|
||||
args.arg1 = message;
|
||||
args.argi2 = modality;
|
||||
args.argi3 = error;
|
||||
args.argi4 = vendorCode;
|
||||
mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
|
||||
}
|
||||
}
|
||||
@@ -666,7 +671,8 @@ public class BiometricService extends SystemService {
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
int error;
|
||||
try {
|
||||
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
|
||||
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
|
||||
opPackageName);
|
||||
error = result.second;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
@@ -675,22 +681,30 @@ public class BiometricService extends SystemService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasEnrolledBiometrics(int userId) {
|
||||
public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
|
||||
checkInternalPermission();
|
||||
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
for (int i = 0; i < mAuthenticators.size(); i++) {
|
||||
if (mAuthenticators.get(i).mAuthenticator.hasEnrolledTemplates(userId)) {
|
||||
for (AuthenticatorWrapper authenticator : mAuthenticators) {
|
||||
if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAuthenticator(int id, int strength, int modality,
|
||||
IBiometricAuthenticator authenticator) {
|
||||
mAuthenticators.add(new AuthenticatorWrapper(id, strength, modality, authenticator));
|
||||
}
|
||||
|
||||
@Override // Binder call
|
||||
public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback)
|
||||
throws RemoteException {
|
||||
@@ -710,9 +724,11 @@ public class BiometricService extends SystemService {
|
||||
checkInternalPermission();
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
for (int i = 0; i < mAuthenticators.size(); i++) {
|
||||
mAuthenticators.get(i).getAuthenticator().setActiveUser(userId);
|
||||
for (AuthenticatorWrapper authenticator : mAuthenticators) {
|
||||
authenticator.impl.setActiveUser(userId);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
@@ -723,11 +739,8 @@ public class BiometricService extends SystemService {
|
||||
checkInternalPermission();
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
if (mFingerprintService != null) {
|
||||
mFingerprintService.resetTimeout(token);
|
||||
}
|
||||
if (mFaceService != null) {
|
||||
mFaceService.resetLockout(token);
|
||||
for (AuthenticatorWrapper authenticator : mAuthenticators) {
|
||||
authenticator.impl.resetLockout(token);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
@@ -750,40 +763,69 @@ public class BiometricService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for injecting dependencies into BiometricService.
|
||||
* TODO(b/141025588): Replace with a dependency injection framework (e.g. Guice, Dagger).
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static class Injector {
|
||||
IActivityManager getActivityManagerService() {
|
||||
public static class Injector {
|
||||
|
||||
@VisibleForTesting
|
||||
public IActivityManager getActivityManagerService() {
|
||||
return ActivityManager.getService();
|
||||
}
|
||||
|
||||
IStatusBarService getStatusBarService() {
|
||||
@VisibleForTesting
|
||||
public IStatusBarService getStatusBarService() {
|
||||
return IStatusBarService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
|
||||
}
|
||||
|
||||
IFingerprintService getFingerprintService() {
|
||||
return IFingerprintService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.FINGERPRINT_SERVICE));
|
||||
/**
|
||||
* Allows to mock FaceAuthenticator for testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public IBiometricAuthenticator getFingerprintAuthenticator() {
|
||||
return new FingerprintAuthenticator(IFingerprintService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
|
||||
}
|
||||
|
||||
IFaceService getFaceService() {
|
||||
return IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE));
|
||||
/**
|
||||
* Allows to mock FaceAuthenticator for testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public IBiometricAuthenticator getFaceAuthenticator() {
|
||||
return new FaceAuthenticator(
|
||||
IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE)));
|
||||
}
|
||||
|
||||
SettingObserver getSettingObserver(Context context, Handler handler,
|
||||
/**
|
||||
* Allows to mock SettingObserver for testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public SettingObserver getSettingObserver(Context context, Handler handler,
|
||||
List<EnabledOnKeyguardCallback> callbacks) {
|
||||
return new SettingObserver(context, handler, callbacks);
|
||||
}
|
||||
|
||||
KeyStore getKeyStore() {
|
||||
@VisibleForTesting
|
||||
public KeyStore getKeyStore() {
|
||||
return KeyStore.getInstance();
|
||||
}
|
||||
|
||||
boolean isDebugEnabled(Context context, int userId) {
|
||||
/**
|
||||
* Allows to enable/disable debug logs.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public boolean isDebugEnabled(Context context, int userId) {
|
||||
return Utils.isDebugEnabled(context, userId);
|
||||
}
|
||||
|
||||
void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
|
||||
/**
|
||||
* Allows to stub publishBinderService(...) for testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
|
||||
service.publishBinderService(Context.BIOMETRIC_SERVICE, impl);
|
||||
}
|
||||
}
|
||||
@@ -812,9 +854,9 @@ public class BiometricService extends SystemService {
|
||||
mEnabledOnKeyguardCallbacks);
|
||||
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
|
||||
mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
|
||||
mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
|
||||
mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
|
||||
|
||||
try {
|
||||
injector.getActivityManagerService().registerUserSwitchObserver(
|
||||
@@ -833,26 +875,30 @@ public class BiometricService extends SystemService {
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
// TODO: maybe get these on-demand
|
||||
if (mHasFeatureFingerprint) {
|
||||
mFingerprintService = mInjector.getFingerprintService();
|
||||
}
|
||||
if (mHasFeatureFace) {
|
||||
mFaceService = mInjector.getFaceService();
|
||||
// TODO(b/141025588): remove this code block once AuthService is integrated.
|
||||
{
|
||||
if (mHasFeatureFace) {
|
||||
try {
|
||||
mImpl.registerAuthenticator(0, 0, TYPE_FACE, mInjector.getFaceAuthenticator());
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
}
|
||||
if (mHasFeatureFingerprint) {
|
||||
try {
|
||||
mImpl.registerAuthenticator(0, 0, TYPE_FINGERPRINT,
|
||||
mInjector.getFingerprintAuthenticator());
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
}
|
||||
if (mHasFeatureIris) {
|
||||
Slog.e(TAG, "Iris is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
mKeyStore = mInjector.getKeyStore();
|
||||
mStatusBarService = mInjector.getStatusBarService();
|
||||
|
||||
// Cache the authenticators
|
||||
for (int featureId : FEATURE_ID) {
|
||||
if (hasFeature(featureId)) {
|
||||
AuthenticatorWrapper authenticator =
|
||||
new AuthenticatorWrapper(featureId, getAuthenticator(featureId));
|
||||
mAuthenticators.add(authenticator);
|
||||
}
|
||||
}
|
||||
|
||||
mInjector.publishBinderService(this, mImpl);
|
||||
}
|
||||
|
||||
@@ -868,7 +914,7 @@ public class BiometricService extends SystemService {
|
||||
* {@link BiometricAuthenticator#TYPE_FACE}
|
||||
* and the error containing one of the {@link BiometricConstants} errors.
|
||||
*/
|
||||
private Pair<Integer, Integer> checkAndGetBiometricModality(int userId) {
|
||||
private Pair<Integer, Integer> checkAndGetBiometricModality(int userId, String opPackageName) {
|
||||
// No biometric features, send error
|
||||
if (mAuthenticators.isEmpty()) {
|
||||
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
|
||||
@@ -886,23 +932,26 @@ public class BiometricService extends SystemService {
|
||||
|
||||
int modality = TYPE_NONE;
|
||||
int firstHwAvailable = TYPE_NONE;
|
||||
for (AuthenticatorWrapper authenticatorWrapper : mAuthenticators) {
|
||||
modality = authenticatorWrapper.getType();
|
||||
BiometricAuthenticator authenticator = authenticatorWrapper.getAuthenticator();
|
||||
if (authenticator.isHardwareDetected()) {
|
||||
isHardwareDetected = true;
|
||||
if (firstHwAvailable == TYPE_NONE) {
|
||||
// Store the first one since we want to return the error in correct priority
|
||||
// order.
|
||||
firstHwAvailable = modality;
|
||||
}
|
||||
if (authenticator.hasEnrolledTemplates(userId)) {
|
||||
hasTemplatesEnrolled = true;
|
||||
if (isEnabledForApp(modality, userId)) {
|
||||
enabledForApps = true;
|
||||
break;
|
||||
for (AuthenticatorWrapper authenticator : mAuthenticators) {
|
||||
modality = authenticator.modality;
|
||||
try {
|
||||
if (authenticator.impl.isHardwareDetected(opPackageName)) {
|
||||
isHardwareDetected = true;
|
||||
if (firstHwAvailable == TYPE_NONE) {
|
||||
// Store the first one since we want to return the error in correct priority
|
||||
// order.
|
||||
firstHwAvailable = modality;
|
||||
}
|
||||
if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
|
||||
hasTemplatesEnrolled = true;
|
||||
if (isEnabledForApp(modality, userId)) {
|
||||
enabledForApps = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -926,7 +975,7 @@ public class BiometricService extends SystemService {
|
||||
}
|
||||
|
||||
private boolean isEnabledForApp(int modality, int userId) {
|
||||
switch(modality) {
|
||||
switch (modality) {
|
||||
case TYPE_FINGERPRINT:
|
||||
return true;
|
||||
case TYPE_IRIS:
|
||||
@@ -939,47 +988,16 @@ public class BiometricService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
private String getErrorString(int type, int error, int vendorCode) {
|
||||
switch (type) {
|
||||
case TYPE_FINGERPRINT:
|
||||
return FingerprintManager.getErrorString(getContext(), error, vendorCode);
|
||||
case TYPE_IRIS:
|
||||
Slog.w(TAG, "Modality not supported");
|
||||
return null; // not supported
|
||||
case TYPE_FACE:
|
||||
return FaceManager.getErrorString(getContext(), error, vendorCode);
|
||||
default:
|
||||
Slog.w(TAG, "Unable to get error string for modality: " + type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private BiometricAuthenticator getAuthenticator(int type) {
|
||||
switch (type) {
|
||||
case TYPE_FINGERPRINT:
|
||||
return (FingerprintManager)
|
||||
getContext().getSystemService(Context.FINGERPRINT_SERVICE);
|
||||
case TYPE_IRIS:
|
||||
return null;
|
||||
case TYPE_FACE:
|
||||
return (FaceManager)
|
||||
getContext().getSystemService(Context.FACE_SERVICE);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasFeature(int type) {
|
||||
switch (type) {
|
||||
case TYPE_FINGERPRINT:
|
||||
return mHasFeatureFingerprint;
|
||||
case TYPE_IRIS:
|
||||
return mHasFeatureIris;
|
||||
case TYPE_FACE:
|
||||
return mHasFeatureFace;
|
||||
default:
|
||||
return false;
|
||||
private String getErrorString(int modality, int error, int vendorCode) {
|
||||
for (AuthenticatorWrapper authenticator : mAuthenticators) {
|
||||
if (authenticator.modality == modality) {
|
||||
// TODO(b/141025588): Refactor IBiometricServiceReceiver.aidl#onError(...) to not
|
||||
// ask for a String error message, but derive it from the error code instead.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
Slog.w(TAG, "Unable to get error string for modality: " + modality);
|
||||
return null;
|
||||
}
|
||||
|
||||
private void logDialogDismissed(int reason) {
|
||||
@@ -1081,14 +1099,14 @@ public class BiometricService extends SystemService {
|
||||
|
||||
// Notify SysUI that the biometric has been authenticated. SysUI already knows
|
||||
// the implicit/explicit state and will react accordingly.
|
||||
mStatusBarService.onBiometricAuthenticated(true, null /* failureReason */);
|
||||
mStatusBarService.onBiometricAuthenticated();
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAuthenticationRejected(String failureReason) {
|
||||
Slog.v(TAG, "handleAuthenticationRejected: " + failureReason);
|
||||
private void handleAuthenticationRejected() {
|
||||
Slog.v(TAG, "handleAuthenticationRejected()");
|
||||
try {
|
||||
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
|
||||
// after user dismissed/canceled dialog).
|
||||
@@ -1097,7 +1115,8 @@ public class BiometricService extends SystemService {
|
||||
return;
|
||||
}
|
||||
|
||||
mStatusBarService.onBiometricAuthenticated(false, failureReason);
|
||||
mStatusBarService.onBiometricError(TYPE_NONE,
|
||||
BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */);
|
||||
|
||||
// TODO: This logic will need to be updated if BP is multi-modal
|
||||
if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
|
||||
@@ -1112,8 +1131,9 @@ public class BiometricService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAuthenticationTimedOut(String message) {
|
||||
Slog.v(TAG, "handleAuthenticationTimedOut: " + message);
|
||||
private void handleAuthenticationTimedOut(int modality, int error, int vendorCode) {
|
||||
Slog.v(TAG, String.format("handleAuthenticationTimedOut(%d, %d, %d)", modality, error,
|
||||
vendorCode));
|
||||
try {
|
||||
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
|
||||
// after user dismissed/canceled dialog).
|
||||
@@ -1122,14 +1142,14 @@ public class BiometricService extends SystemService {
|
||||
return;
|
||||
}
|
||||
|
||||
mStatusBarService.onBiometricAuthenticated(false, message);
|
||||
mStatusBarService.onBiometricError(modality, error, vendorCode);
|
||||
mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleOnError(int cookie, int error, String message) {
|
||||
private void handleOnError(int cookie, int modality, int error, int vendorCode) {
|
||||
Slog.d(TAG, "handleOnError: " + 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
|
||||
@@ -1140,7 +1160,7 @@ public class BiometricService extends SystemService {
|
||||
try {
|
||||
if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
|
||||
mCurrentAuthSession.mErrorEscrow = error;
|
||||
mCurrentAuthSession.mErrorStringEscrow = message;
|
||||
mCurrentAuthSession.mVendorCodeEscrow = vendorCode;
|
||||
|
||||
if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
|
||||
final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
|
||||
@@ -1148,20 +1168,20 @@ public class BiometricService extends SystemService {
|
||||
if (mCurrentAuthSession.isAllowDeviceCredential() && errorLockout) {
|
||||
// SystemUI handles transition from biometric to device credential.
|
||||
mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
|
||||
mStatusBarService.onBiometricError(error, message);
|
||||
mStatusBarService.onBiometricError(modality, error, vendorCode);
|
||||
} else {
|
||||
mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI;
|
||||
if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
|
||||
mStatusBarService.hideAuthenticationDialog();
|
||||
} else {
|
||||
mStatusBarService.onBiometricError(error, message);
|
||||
mStatusBarService.onBiometricError(modality, error, vendorCode);
|
||||
}
|
||||
}
|
||||
} else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) {
|
||||
// In the "try again" state, we should forward canceled errors to
|
||||
// the client and and clean up. The only error we should get here is
|
||||
// ERROR_CANCELED due to another client kicking us out.
|
||||
mCurrentAuthSession.mClientReceiver.onError(error, message);
|
||||
mCurrentAuthSession.mClientReceiver.onError(modality, error, vendorCode);
|
||||
mStatusBarService.hideAuthenticationDialog();
|
||||
mCurrentAuthSession = null;
|
||||
} else if (mCurrentAuthSession.mState == STATE_SHOWING_DEVICE_CREDENTIAL) {
|
||||
@@ -1197,7 +1217,7 @@ public class BiometricService extends SystemService {
|
||||
mCurrentAuthSession.mUserId,
|
||||
mCurrentAuthSession.mOpPackageName);
|
||||
} else {
|
||||
mPendingAuthSession.mClientReceiver.onError(error, message);
|
||||
mPendingAuthSession.mClientReceiver.onError(modality, error, vendorCode);
|
||||
mPendingAuthSession = null;
|
||||
}
|
||||
} else {
|
||||
@@ -1261,8 +1281,10 @@ public class BiometricService extends SystemService {
|
||||
|
||||
case BiometricPrompt.DISMISSED_REASON_USER_CANCEL:
|
||||
mCurrentAuthSession.mClientReceiver.onError(
|
||||
mCurrentAuthSession.mModality,
|
||||
BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
|
||||
getContext().getString(R.string.biometric_error_user_canceled));
|
||||
0 /* vendorCode */
|
||||
);
|
||||
// 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 */);
|
||||
@@ -1270,8 +1292,11 @@ public class BiometricService extends SystemService {
|
||||
|
||||
case BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED:
|
||||
case BiometricPrompt.DISMISSED_REASON_ERROR:
|
||||
mCurrentAuthSession.mClientReceiver.onError(mCurrentAuthSession.mErrorEscrow,
|
||||
mCurrentAuthSession.mErrorStringEscrow);
|
||||
mCurrentAuthSession.mClientReceiver.onError(
|
||||
mCurrentAuthSession.mModality,
|
||||
mCurrentAuthSession.mErrorEscrow,
|
||||
mCurrentAuthSession.mVendorCodeEscrow
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1354,30 +1379,35 @@ public class BiometricService extends SystemService {
|
||||
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());
|
||||
int modality = TYPE_NONE;
|
||||
it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
|
||||
boolean foundAuthenticator = false;
|
||||
for (AuthenticatorWrapper authenticator : mAuthenticators) {
|
||||
if (authenticator.modality == pair.getKey()) {
|
||||
foundAuthenticator = true;
|
||||
try {
|
||||
authenticator.impl.startPreparedClient(pair.getValue());
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
}
|
||||
modality |= pair.getKey();
|
||||
}
|
||||
if (!foundAuthenticator) {
|
||||
Slog.e(TAG, "Unknown modality: " + pair.getKey());
|
||||
}
|
||||
modality |= pair.getKey();
|
||||
}
|
||||
|
||||
if (!continuing) {
|
||||
if (!continuing) {
|
||||
try {
|
||||
mStatusBarService.showAuthenticationDialog(mCurrentAuthSession.mBundle,
|
||||
mInternalReceiver, modality, requireConfirmation, userId,
|
||||
mCurrentAuthSession.mOpPackageName);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Remote exception", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1387,7 +1417,8 @@ public class BiometricService extends SystemService {
|
||||
int callingUid, int callingPid, int callingUserId) {
|
||||
|
||||
mHandler.post(() -> {
|
||||
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
|
||||
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
|
||||
opPackageName);
|
||||
final int modality = result.first;
|
||||
final int error = result.second;
|
||||
|
||||
@@ -1400,23 +1431,7 @@ public class BiometricService extends SystemService {
|
||||
} else if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
|
||||
// Check for errors, notify callback, and return
|
||||
try {
|
||||
final String hardwareUnavailable =
|
||||
getContext().getString(R.string.biometric_error_hw_unavailable);
|
||||
switch (error) {
|
||||
case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
|
||||
receiver.onError(error, hardwareUnavailable);
|
||||
break;
|
||||
case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
|
||||
receiver.onError(error, hardwareUnavailable);
|
||||
break;
|
||||
case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
|
||||
receiver.onError(error,
|
||||
getErrorString(modality, error, 0 /* vendorCode */));
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Unhandled error");
|
||||
break;
|
||||
}
|
||||
receiver.onError(modality, error, 0 /* vendorCode */);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to send error", e);
|
||||
}
|
||||
@@ -1435,41 +1450,41 @@ public class BiometricService extends SystemService {
|
||||
* 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
|
||||
* 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"
|
||||
* 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) {
|
||||
boolean requireConfirmation = bundle.getBoolean(
|
||||
BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
|
||||
if ((modality & TYPE_FACE) != 0) {
|
||||
// Check if the user has forced confirmation to be required in Settings.
|
||||
requireConfirmation = requireConfirmation
|
||||
|| mSettingObserver.getFaceAlwaysRequireConfirmation(userId);
|
||||
}
|
||||
// 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;
|
||||
final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
|
||||
Slog.d(TAG, "Creating auth session. Modality: " + modality
|
||||
+ ", cookie: " + cookie
|
||||
+ ", authenticators: " + authenticators);
|
||||
final HashMap<Integer, Integer> modalities = new HashMap<>();
|
||||
|
||||
// If it's only device credential, we don't need to wait - LockSettingsService is
|
||||
// always ready to check credential (SystemUI invokes that path).
|
||||
if ((authenticators & ~Authenticator.TYPE_CREDENTIAL) != 0) {
|
||||
modalities.put(modality, cookie);
|
||||
}
|
||||
mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
|
||||
receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
|
||||
modality, requireConfirmation);
|
||||
|
||||
try {
|
||||
boolean requireConfirmation = bundle.getBoolean(
|
||||
BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
|
||||
if ((modality & TYPE_FACE) != 0) {
|
||||
// Check if the user has forced confirmation to be required in Settings.
|
||||
requireConfirmation = requireConfirmation
|
||||
|| mSettingObserver.getFaceAlwaysRequireConfirmation(userId);
|
||||
}
|
||||
// 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;
|
||||
final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
|
||||
Slog.d(TAG, "Creating auth session. Modality: " + modality
|
||||
+ ", cookie: " + cookie
|
||||
+ ", authenticators: " + authenticators);
|
||||
final HashMap<Integer, Integer> modalities = new HashMap<>();
|
||||
|
||||
// If it's only device credential, we don't need to wait - LockSettingsService is
|
||||
// always ready to check credential (SystemUI invokes that path).
|
||||
if ((authenticators & ~Authenticator.TYPE_CREDENTIAL) != 0) {
|
||||
modalities.put(modality, cookie);
|
||||
}
|
||||
mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
|
||||
receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
|
||||
modality, requireConfirmation);
|
||||
|
||||
if (authenticators == Authenticator.TYPE_CREDENTIAL) {
|
||||
mPendingAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
|
||||
mCurrentAuthSession = mPendingAuthSession;
|
||||
@@ -1484,19 +1499,10 @@ public class BiometricService extends SystemService {
|
||||
mCurrentAuthSession.mOpPackageName);
|
||||
} else {
|
||||
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(requireConfirmation,
|
||||
token, sessionId, userId, mInternalReceiver, opPackageName,
|
||||
cookie, callingUid, callingPid, callingUserId);
|
||||
for (AuthenticatorWrapper authenticator : mAuthenticators) {
|
||||
authenticator.impl.prepareForAuthentication(requireConfirmation, token,
|
||||
sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid,
|
||||
callingPid, callingUserId);
|
||||
}
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
@@ -1517,11 +1523,10 @@ public class BiometricService extends SystemService {
|
||||
try {
|
||||
// Send error to client
|
||||
mCurrentAuthSession.mClientReceiver.onError(
|
||||
mCurrentAuthSession.mModality,
|
||||
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
|
||||
getContext().getString(
|
||||
com.android.internal.R.string.biometric_error_user_canceled)
|
||||
0 /* vendorCode */
|
||||
);
|
||||
|
||||
mCurrentAuthSession = null;
|
||||
mStatusBarService.hideAuthenticationDialog();
|
||||
} catch (RemoteException e) {
|
||||
@@ -1537,30 +1542,25 @@ public class BiometricService extends SystemService {
|
||||
final int callingPid = Binder.getCallingPid();
|
||||
final int callingUserId = UserHandle.getCallingUserId();
|
||||
|
||||
try {
|
||||
if (mCurrentAuthSession == null) {
|
||||
Slog.w(TAG, "Skipping cancelInternal");
|
||||
return;
|
||||
} else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
|
||||
Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
|
||||
return;
|
||||
}
|
||||
if (mCurrentAuthSession == null) {
|
||||
Slog.w(TAG, "Skipping cancelInternal");
|
||||
return;
|
||||
} else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
|
||||
Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: For multiple modalities, send a single ERROR_CANCELED only when all
|
||||
// drivers have canceled authentication.
|
||||
if ((mCurrentAuthSession.mModality & TYPE_FINGERPRINT) != 0) {
|
||||
mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
|
||||
callingUid, callingPid, callingUserId, fromClient);
|
||||
// TODO: For multiple modalities, send a single ERROR_CANCELED only when all
|
||||
// drivers have canceled authentication.
|
||||
for (AuthenticatorWrapper authenticator : mAuthenticators) {
|
||||
if ((authenticator.modality & mCurrentAuthSession.mModality) != 0) {
|
||||
try {
|
||||
authenticator.impl.cancelAuthenticationFromService(token, opPackageName,
|
||||
callingUid, callingPid, callingUserId, fromClient);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to cancel authentication");
|
||||
}
|
||||
}
|
||||
if ((mCurrentAuthSession.mModality & TYPE_IRIS) != 0) {
|
||||
Slog.w(TAG, "Iris unsupported");
|
||||
}
|
||||
if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
|
||||
mFaceService.cancelAuthenticationFromService(token, opPackageName,
|
||||
callingUid, callingPid, callingUserId, fromClient);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to cancel authentication");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.face;
|
||||
|
||||
import android.hardware.biometrics.IBiometricAuthenticator;
|
||||
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
|
||||
import android.hardware.face.IFaceService;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
/**
|
||||
* TODO(b/141025588): Add JavaDoc.
|
||||
*/
|
||||
public final class FaceAuthenticator extends IBiometricAuthenticator.Stub {
|
||||
private final IFaceService mFaceService;
|
||||
|
||||
public FaceAuthenticator(IFaceService faceService) {
|
||||
mFaceService = faceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
|
||||
long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
|
||||
String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
|
||||
throws RemoteException {
|
||||
mFaceService.prepareForAuthentication(requireConfirmation, token, sessionId, userId,
|
||||
wrapperReceiver, opPackageName, cookie, callingUid, callingPid, callingUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPreparedClient(int cookie) throws RemoteException {
|
||||
mFaceService.startPreparedClient(cookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
|
||||
int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
|
||||
mFaceService.cancelAuthenticationFromService(token, opPackageName, callingUid, callingPid,
|
||||
callingUserId, fromClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHardwareDetected(String opPackageName) throws RemoteException {
|
||||
return mFaceService.isHardwareDetected(opPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
|
||||
return mFaceService.hasEnrolledFaces(userId, opPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetLockout(byte[] token) throws RemoteException {
|
||||
mFaceService.resetLockout(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActiveUser(int uid) throws RemoteException {
|
||||
mFaceService.setActiveUser(uid);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS;
|
||||
import static android.Manifest.permission.MANAGE_BIOMETRIC;
|
||||
import static android.Manifest.permission.RESET_FACE_LOCKOUT;
|
||||
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AppOpsManager;
|
||||
@@ -538,7 +539,7 @@ public class FaceService extends BiometricServiceBase {
|
||||
|
||||
// TODO: refactor out common code here
|
||||
@Override // Binder call
|
||||
public boolean isHardwareDetected(long deviceId, String opPackageName) {
|
||||
public boolean isHardwareDetected(String opPackageName) {
|
||||
checkPermission(USE_BIOMETRIC_INTERNAL);
|
||||
if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
|
||||
Binder.getCallingUid(), Binder.getCallingPid(),
|
||||
@@ -752,8 +753,7 @@ public class FaceService extends BiometricServiceBase {
|
||||
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));
|
||||
getWrapperReceiver().onError(cookie, TYPE_FACE, error, vendorCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.fingerprint;
|
||||
|
||||
import android.hardware.biometrics.IBiometricAuthenticator;
|
||||
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
|
||||
import android.hardware.fingerprint.IFingerprintService;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
/**
|
||||
* TODO(b/141025588): Add JavaDoc.
|
||||
*/
|
||||
public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub {
|
||||
private final IFingerprintService mFingerprintService;
|
||||
|
||||
public FingerprintAuthenticator(IFingerprintService fingerprintService) {
|
||||
mFingerprintService = fingerprintService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
|
||||
long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
|
||||
String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
|
||||
throws RemoteException {
|
||||
mFingerprintService.prepareForAuthentication(token, sessionId, userId, wrapperReceiver,
|
||||
opPackageName, cookie, callingUid, callingPid, callingUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPreparedClient(int cookie) throws RemoteException {
|
||||
mFingerprintService.startPreparedClient(cookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
|
||||
int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
|
||||
mFingerprintService.cancelAuthenticationFromService(token, opPackageName, callingUid,
|
||||
callingPid, callingUserId, fromClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHardwareDetected(String opPackageName) throws RemoteException {
|
||||
return mFingerprintService.isHardwareDetected(opPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
|
||||
return mFingerprintService.hasEnrolledFingerprints(userId, opPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetLockout(byte[] token) throws RemoteException {
|
||||
mFingerprintService.resetTimeout(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActiveUser(int uid) throws RemoteException {
|
||||
mFingerprintService.setActiveUser(uid);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import static android.Manifest.permission.MANAGE_FINGERPRINT;
|
||||
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
|
||||
import static android.Manifest.permission.USE_BIOMETRIC;
|
||||
import static android.Manifest.permission.USE_FINGERPRINT;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlarmManager;
|
||||
@@ -350,7 +351,7 @@ public class FingerprintService extends BiometricServiceBase {
|
||||
|
||||
// TODO: refactor out common code here
|
||||
@Override // Binder call
|
||||
public boolean isHardwareDetected(long deviceId, String opPackageName) {
|
||||
public boolean isHardwareDetected(String opPackageName) {
|
||||
if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
|
||||
Binder.getCallingUid(), Binder.getCallingPid(),
|
||||
UserHandle.getCallingUserId())) {
|
||||
@@ -480,8 +481,7 @@ public class FingerprintService extends BiometricServiceBase {
|
||||
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));
|
||||
getWrapperReceiver().onError(cookie, TYPE_FINGERPRINT, error, vendorCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.biometrics.iris;
|
||||
|
||||
import android.hardware.biometrics.IBiometricAuthenticator;
|
||||
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
|
||||
import android.hardware.iris.IIrisService;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
/**
|
||||
* TODO(b/141025588): Add JavaDoc.
|
||||
*/
|
||||
public final class IrisAuthenticator extends IBiometricAuthenticator.Stub {
|
||||
private final IIrisService mIrisService;
|
||||
|
||||
public IrisAuthenticator(IIrisService irisService) {
|
||||
mIrisService = irisService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
|
||||
long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
|
||||
String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
|
||||
throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPreparedClient(int cookie) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
|
||||
int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHardwareDetected(String opPackageName) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetLockout(byte[] token) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActiveUser(int uid) throws RemoteException {
|
||||
}
|
||||
}
|
||||
@@ -655,11 +655,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
|
||||
public void onBiometricAuthenticated() {
|
||||
enforceBiometricDialog();
|
||||
if (mBar != null) {
|
||||
try {
|
||||
mBar.onBiometricAuthenticated(authenticated, failureReason);
|
||||
mBar.onBiometricAuthenticated();
|
||||
} catch (RemoteException ex) {
|
||||
}
|
||||
}
|
||||
@@ -677,11 +677,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBiometricError(int errorCode, String error) {
|
||||
public void onBiometricError(int modality, int error, int vendorCode) {
|
||||
enforceBiometricDialog();
|
||||
if (mBar != null) {
|
||||
try {
|
||||
mBar.onBiometricError(errorCode, error);
|
||||
mBar.onBiometricError(modality, error, vendorCode);
|
||||
} catch (RemoteException ex) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.IActivityManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -43,17 +42,15 @@ import android.hardware.biometrics.Authenticator;
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.hardware.biometrics.BiometricConstants;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.hardware.biometrics.IBiometricAuthenticator;
|
||||
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;
|
||||
import android.hardware.fingerprint.IFingerprintService;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.security.KeyStore;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
@@ -68,8 +65,6 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
public class BiometricServiceTest {
|
||||
|
||||
@@ -98,71 +93,33 @@ public class BiometricServiceTest {
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
@Mock
|
||||
private AppOpsManager mAppOpsManager;
|
||||
@Mock
|
||||
IBiometricServiceReceiver mReceiver1;
|
||||
@Mock
|
||||
IBiometricServiceReceiver mReceiver2;
|
||||
@Mock
|
||||
FingerprintManager mFingerprintManager;
|
||||
BiometricService.Injector mInjector;
|
||||
@Mock
|
||||
FaceManager mFaceManager;
|
||||
|
||||
private static class MockInjector extends BiometricService.Injector {
|
||||
@Override
|
||||
IActivityManager getActivityManagerService() {
|
||||
return mock(IActivityManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
IStatusBarService getStatusBarService() {
|
||||
return mock(IStatusBarService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
IFingerprintService getFingerprintService() {
|
||||
return mock(IFingerprintService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
IFaceService getFaceService() {
|
||||
return mock(IFaceService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
BiometricService.SettingObserver getSettingObserver(Context context, Handler handler,
|
||||
List<BiometricService.EnabledOnKeyguardCallback> callbacks) {
|
||||
return mock(BiometricService.SettingObserver.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
KeyStore getKeyStore() {
|
||||
return mock(KeyStore.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDebugEnabled(Context context, int userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
|
||||
// no-op for test
|
||||
}
|
||||
}
|
||||
IBiometricAuthenticator mFingerprintAuthenticator;
|
||||
@Mock
|
||||
IBiometricAuthenticator mFaceAuthenticator;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||
when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
|
||||
when(mContext.getSystemService(Context.FINGERPRINT_SERVICE))
|
||||
.thenReturn(mFingerprintManager);
|
||||
when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
|
||||
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
||||
when(mContext.getResources()).thenReturn(mResources);
|
||||
|
||||
when(mInjector.getActivityManagerService()).thenReturn(mock(IActivityManager.class));
|
||||
when(mInjector.getStatusBarService()).thenReturn(mock(IStatusBarService.class));
|
||||
when(mInjector.getFingerprintAuthenticator()).thenReturn(mFingerprintAuthenticator);
|
||||
when(mInjector.getFaceAuthenticator()).thenReturn(mFaceAuthenticator);
|
||||
when(mInjector.getSettingObserver(any(), any(), any())).thenReturn(
|
||||
mock(BiometricService.SettingObserver.class));
|
||||
when(mInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
|
||||
when(mInjector.isDebugEnabled(any(), anyInt())).thenReturn(false);
|
||||
|
||||
when(mResources.getString(R.string.biometric_error_hw_unavailable))
|
||||
.thenReturn(ERROR_HW_UNAVAILABLE);
|
||||
when(mResources.getString(R.string.biometric_not_recognized))
|
||||
@@ -172,61 +129,69 @@ public class BiometricServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws Exception {
|
||||
public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws
|
||||
Exception {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
|
||||
.thenReturn(false);
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(false);
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
|
||||
|
||||
mBiometricService = new BiometricService(mContext, new MockInjector());
|
||||
mBiometricService = new BiometricService(mContext, mInjector);
|
||||
mBiometricService.onStart();
|
||||
|
||||
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
|
||||
false /* allowDeviceCredential */);
|
||||
waitForIdle();
|
||||
verify(mReceiver1).onError(
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT), eq(ERROR_HW_UNAVAILABLE));
|
||||
eq(BiometricAuthenticator.TYPE_NONE),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT),
|
||||
eq(0 /* vendorCode */));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
|
||||
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
|
||||
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
|
||||
|
||||
mBiometricService = new BiometricService(mContext, new MockInjector());
|
||||
mBiometricService = new BiometricService(mContext, mInjector);
|
||||
mBiometricService.onStart();
|
||||
|
||||
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
|
||||
false /* allowDeviceCredential */);
|
||||
waitForIdle();
|
||||
verify(mReceiver1).onError(
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS), any());
|
||||
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS),
|
||||
eq(0 /* vendorCode */));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws Exception {
|
||||
public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws
|
||||
Exception {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
|
||||
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
|
||||
when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
|
||||
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
|
||||
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(false);
|
||||
|
||||
mBiometricService = new BiometricService(mContext, new MockInjector());
|
||||
mBiometricService = new BiometricService(mContext, mInjector);
|
||||
mBiometricService.onStart();
|
||||
|
||||
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
|
||||
false /* allowDeviceCredential */);
|
||||
waitForIdle();
|
||||
verify(mReceiver1).onError(
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE));
|
||||
eq(BiometricAuthenticator.TYPE_NONE),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
|
||||
eq(0 /* vendorCode */));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateFace_respectsUserSetting()
|
||||
throws Exception {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
|
||||
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
|
||||
when(mFaceManager.isHardwareDetected()).thenReturn(true);
|
||||
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
|
||||
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
|
||||
|
||||
mBiometricService = new BiometricService(mContext, new MockInjector());
|
||||
mBiometricService = new BiometricService(mContext, mInjector);
|
||||
mBiometricService.onStart();
|
||||
|
||||
// Disabled in user settings receives onError
|
||||
@@ -235,7 +200,9 @@ public class BiometricServiceTest {
|
||||
false /* allowDeviceCredential */);
|
||||
waitForIdle();
|
||||
verify(mReceiver1).onError(
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE));
|
||||
eq(BiometricAuthenticator.TYPE_NONE),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
|
||||
eq(0 /* vendorCode */));
|
||||
|
||||
// Enrolled, not disabled in settings, user requires confirmation in settings
|
||||
resetReceiver();
|
||||
@@ -245,8 +212,8 @@ public class BiometricServiceTest {
|
||||
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
|
||||
false /* allowDeviceCredential */);
|
||||
waitForIdle();
|
||||
verify(mReceiver1, never()).onError(anyInt(), any(String.class));
|
||||
verify(mBiometricService.mFaceService).prepareForAuthentication(
|
||||
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
|
||||
verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
|
||||
eq(true) /* requireConfirmation */,
|
||||
any(IBinder.class),
|
||||
anyLong() /* sessionId */,
|
||||
@@ -265,7 +232,7 @@ public class BiometricServiceTest {
|
||||
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
|
||||
false /* allowDeviceCredential */);
|
||||
waitForIdle();
|
||||
verify(mBiometricService.mFaceService).prepareForAuthentication(
|
||||
verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
|
||||
eq(false) /* requireConfirmation */,
|
||||
any(IBinder.class),
|
||||
anyLong() /* sessionId */,
|
||||
@@ -281,7 +248,7 @@ public class BiometricServiceTest {
|
||||
@Test
|
||||
public void testAuthenticate_happyPathWithoutConfirmation() throws Exception {
|
||||
setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
|
||||
mBiometricService = new BiometricService(mContext, new MockInjector());
|
||||
mBiometricService = new BiometricService(mContext, mInjector);
|
||||
mBiometricService.onStart();
|
||||
|
||||
// Start testing the happy path
|
||||
@@ -295,8 +262,9 @@ public class BiometricServiceTest {
|
||||
|
||||
// Invokes <Modality>Service#prepareForAuthentication
|
||||
ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
verify(mReceiver1, never()).onError(anyInt(), any(String.class));
|
||||
verify(mBiometricService.mFingerprintService).prepareForAuthentication(
|
||||
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
|
||||
verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
|
||||
anyBoolean() /* requireConfirmation */,
|
||||
any(IBinder.class),
|
||||
anyLong() /* sessionId */,
|
||||
anyInt() /* userId */,
|
||||
@@ -316,7 +284,7 @@ public class BiometricServiceTest {
|
||||
BiometricService.STATE_AUTH_STARTED);
|
||||
|
||||
// startPreparedClient invoked
|
||||
verify(mBiometricService.mFingerprintService)
|
||||
verify(mBiometricService.mAuthenticators.get(0).impl)
|
||||
.startPreparedClient(cookieCaptor.getValue());
|
||||
|
||||
// StatusBar showBiometricDialog invoked
|
||||
@@ -337,8 +305,7 @@ public class BiometricServiceTest {
|
||||
assertEquals(mBiometricService.mCurrentAuthSession.mState,
|
||||
BiometricService.STATE_AUTHENTICATED_PENDING_SYSUI);
|
||||
// Notify SystemUI hardware authenticated
|
||||
verify(mBiometricService.mStatusBarService).onBiometricAuthenticated(
|
||||
eq(true) /* authenticated */, eq(null) /* failureReason */);
|
||||
verify(mBiometricService.mStatusBarService).onBiometricAuthenticated();
|
||||
|
||||
// SystemUI sends callback with dismissed reason
|
||||
mBiometricService.mInternalReceiver.onDialogDismissed(
|
||||
@@ -355,7 +322,7 @@ public class BiometricServiceTest {
|
||||
@Test
|
||||
public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
|
||||
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
|
||||
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
|
||||
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
|
||||
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
|
||||
true /* requireConfirmation */, true /* allowDeviceCredential */);
|
||||
waitForIdle();
|
||||
@@ -409,8 +376,10 @@ public class BiometricServiceTest {
|
||||
mBiometricService.mInternalReceiver.onAuthenticationFailed();
|
||||
waitForIdle();
|
||||
|
||||
verify(mBiometricService.mStatusBarService)
|
||||
.onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED));
|
||||
verify(mBiometricService.mStatusBarService).onBiometricError(
|
||||
eq(BiometricAuthenticator.TYPE_NONE),
|
||||
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
|
||||
eq(0 /* vendorCode */));
|
||||
verify(mReceiver1).onAuthenticationFailed();
|
||||
assertEquals(mBiometricService.mCurrentAuthSession.mState,
|
||||
BiometricService.STATE_AUTH_PAUSED);
|
||||
@@ -426,15 +395,18 @@ public class BiometricServiceTest {
|
||||
mBiometricService.mInternalReceiver.onAuthenticationFailed();
|
||||
waitForIdle();
|
||||
|
||||
verify(mBiometricService.mStatusBarService)
|
||||
.onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED));
|
||||
verify(mBiometricService.mStatusBarService).onBiometricError(
|
||||
eq(BiometricAuthenticator.TYPE_NONE),
|
||||
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
|
||||
eq(0 /* vendorCode */));
|
||||
verify(mReceiver1).onAuthenticationFailed();
|
||||
assertEquals(mBiometricService.mCurrentAuthSession.mState,
|
||||
BiometricService.STATE_AUTH_STARTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws Exception {
|
||||
public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws
|
||||
Exception {
|
||||
setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
|
||||
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
|
||||
false /* requireConfirmation */, false /* allowDeviceCredential */);
|
||||
@@ -451,15 +423,15 @@ public class BiometricServiceTest {
|
||||
BiometricService.STATE_AUTH_STARTED);
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
|
||||
BiometricConstants.BIOMETRIC_ERROR_CANCELED, ERROR_CANCELED);
|
||||
BiometricAuthenticator.TYPE_FINGERPRINT,
|
||||
BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
|
||||
waitForIdle();
|
||||
|
||||
// Auth session doesn't become null until SystemUI responds that the animation is completed
|
||||
assertNotNull(mBiometricService.mCurrentAuthSession);
|
||||
// ERROR_CANCELED is not sent until SystemUI responded that animation is completed
|
||||
verify(mReceiver1, never()).onError(
|
||||
anyInt(), anyString());
|
||||
verify(mReceiver2, never()).onError(anyInt(), any(String.class));
|
||||
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
|
||||
verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
|
||||
|
||||
// SystemUI dialog closed
|
||||
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
|
||||
@@ -469,8 +441,9 @@ public class BiometricServiceTest {
|
||||
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
|
||||
waitForIdle();
|
||||
verify(mReceiver1).onError(
|
||||
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
|
||||
eq(ERROR_CANCELED));
|
||||
eq(0 /* vendorCode */));
|
||||
assertNull(mBiometricService.mCurrentAuthSession);
|
||||
}
|
||||
|
||||
@@ -482,14 +455,17 @@ public class BiometricServiceTest {
|
||||
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
|
||||
BiometricAuthenticator.TYPE_FACE,
|
||||
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
|
||||
ERROR_TIMEOUT);
|
||||
0 /* vendorCode */);
|
||||
waitForIdle();
|
||||
|
||||
assertEquals(mBiometricService.mCurrentAuthSession.mState,
|
||||
BiometricService.STATE_AUTH_PAUSED);
|
||||
verify(mBiometricService.mStatusBarService)
|
||||
.onBiometricAuthenticated(eq(false), eq(ERROR_TIMEOUT));
|
||||
verify(mBiometricService.mStatusBarService).onBiometricError(
|
||||
eq(BiometricAuthenticator.TYPE_FACE),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
|
||||
eq(0 /* vendorCode */));
|
||||
// Timeout does not count as fail as per BiometricPrompt documentation.
|
||||
verify(mReceiver1, never()).onAuthenticationFailed();
|
||||
|
||||
@@ -524,22 +500,25 @@ public class BiometricServiceTest {
|
||||
public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
|
||||
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
|
||||
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
|
||||
false /* requireCOnfirmation */, false /* allowDeviceCredential */);
|
||||
false /* requireConfirmation */, false /* allowDeviceCredential */);
|
||||
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
|
||||
BiometricAuthenticator.TYPE_FACE,
|
||||
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
|
||||
ERROR_TIMEOUT);
|
||||
0 /* vendorCode */);
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
|
||||
BiometricAuthenticator.TYPE_FACE,
|
||||
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
|
||||
ERROR_CANCELED);
|
||||
0 /* vendorCode */);
|
||||
waitForIdle();
|
||||
|
||||
// Client receives error immediately
|
||||
verify(mReceiver1).onError(
|
||||
eq(BiometricAuthenticator.TYPE_FACE),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
|
||||
eq(ERROR_CANCELED));
|
||||
eq(0 /* vendorCode */));
|
||||
// Dialog is hidden immediately
|
||||
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
|
||||
// Auth session is over
|
||||
@@ -558,26 +537,29 @@ public class BiometricServiceTest {
|
||||
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
|
||||
BiometricAuthenticator.TYPE_FINGERPRINT,
|
||||
BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
|
||||
ERROR_UNABLE_TO_PROCESS);
|
||||
0 /* vendorCode */);
|
||||
waitForIdle();
|
||||
|
||||
// Sends error to SystemUI and does not notify client yet
|
||||
assertEquals(mBiometricService.mCurrentAuthSession.mState,
|
||||
BiometricService.STATE_ERROR_PENDING_SYSUI);
|
||||
verify(mBiometricService.mStatusBarService).onBiometricError(
|
||||
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
|
||||
eq(ERROR_UNABLE_TO_PROCESS));
|
||||
eq(0 /* vendorCode */));
|
||||
verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
|
||||
verify(mReceiver1, never()).onError(anyInt(), anyString());
|
||||
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
|
||||
|
||||
// SystemUI animation completed, client is notified, auth session is over
|
||||
mBiometricService.mInternalReceiver
|
||||
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
|
||||
waitForIdle();
|
||||
verify(mReceiver1).onError(
|
||||
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
|
||||
eq(ERROR_UNABLE_TO_PROCESS));
|
||||
eq(0 /* vendorCode */));
|
||||
assertNull(mBiometricService.mCurrentAuthSession);
|
||||
}
|
||||
|
||||
@@ -590,8 +572,9 @@ public class BiometricServiceTest {
|
||||
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForPendingSession(mBiometricService.mPendingAuthSession),
|
||||
BiometricAuthenticator.TYPE_FACE,
|
||||
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
|
||||
ERROR_LOCKOUT);
|
||||
0 /* vendorCode */);
|
||||
waitForIdle();
|
||||
|
||||
// Pending auth session becomes current auth session, since device credential should
|
||||
@@ -622,8 +605,9 @@ public class BiometricServiceTest {
|
||||
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForPendingSession(mBiometricService.mPendingAuthSession),
|
||||
BiometricAuthenticator.TYPE_FINGERPRINT,
|
||||
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
|
||||
ERROR_LOCKOUT);
|
||||
0 /* vendorCode */);
|
||||
waitForIdle();
|
||||
|
||||
// Error is sent to client
|
||||
@@ -690,17 +674,18 @@ public class BiometricServiceTest {
|
||||
|
||||
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
|
||||
mBiometricService.mCurrentAuthSession.mState);
|
||||
verify(mReceiver1, never()).onError(anyInt(), anyString());
|
||||
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
|
||||
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
|
||||
BiometricAuthenticator.TYPE_FINGERPRINT,
|
||||
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
|
||||
ERROR_CANCELED);
|
||||
0 /* vendorCode */);
|
||||
waitForIdle();
|
||||
|
||||
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
|
||||
mBiometricService.mCurrentAuthSession.mState);
|
||||
verify(mReceiver1, never()).onError(anyInt(), anyString());
|
||||
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -714,15 +699,17 @@ public class BiometricServiceTest {
|
||||
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
|
||||
BiometricAuthenticator.TYPE_FINGERPRINT,
|
||||
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
|
||||
ERROR_LOCKOUT);
|
||||
0 /* vendorCode */);
|
||||
waitForIdle();
|
||||
|
||||
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
|
||||
mBiometricService.mCurrentAuthSession.mState);
|
||||
verify(mBiometricService.mStatusBarService).onBiometricError(
|
||||
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT),
|
||||
eq(ERROR_LOCKOUT));
|
||||
eq(0 /* vendorCode */));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -736,15 +723,17 @@ public class BiometricServiceTest {
|
||||
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
|
||||
BiometricAuthenticator.TYPE_FINGERPRINT,
|
||||
BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
|
||||
ERROR_UNABLE_TO_PROCESS);
|
||||
0 /* vendorCode */);
|
||||
waitForIdle();
|
||||
|
||||
assertEquals(BiometricService.STATE_ERROR_PENDING_SYSUI,
|
||||
mBiometricService.mCurrentAuthSession.mState);
|
||||
verify(mBiometricService.mStatusBarService).onBiometricError(
|
||||
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
|
||||
eq(ERROR_UNABLE_TO_PROCESS));
|
||||
eq(0 /* vendorCode */));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -758,9 +747,10 @@ public class BiometricServiceTest {
|
||||
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
|
||||
waitForIdle();
|
||||
verify(mReceiver1).onError(
|
||||
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
|
||||
eq(ERROR_USER_CANCELED));
|
||||
verify(mBiometricService.mFingerprintService).cancelAuthenticationFromService(
|
||||
eq(0 /* vendorCode */));
|
||||
verify(mBiometricService.mAuthenticators.get(0).impl).cancelAuthenticationFromService(
|
||||
any(),
|
||||
any(),
|
||||
anyInt(),
|
||||
@@ -778,13 +768,15 @@ public class BiometricServiceTest {
|
||||
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
|
||||
BiometricAuthenticator.TYPE_FACE,
|
||||
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
|
||||
ERROR_TIMEOUT);
|
||||
0 /* vendorCode */);
|
||||
mBiometricService.mInternalReceiver.onDialogDismissed(
|
||||
BiometricPrompt.DISMISSED_REASON_NEGATIVE);
|
||||
waitForIdle();
|
||||
|
||||
verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
|
||||
verify(mBiometricService.mAuthenticators.get(0).impl,
|
||||
never()).cancelAuthenticationFromService(
|
||||
any(),
|
||||
any(),
|
||||
anyInt(),
|
||||
@@ -794,20 +786,23 @@ public class BiometricServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws Exception {
|
||||
public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws
|
||||
Exception {
|
||||
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
|
||||
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
|
||||
false /* requireConfirmation */, false /* allowDeviceCredential */);
|
||||
|
||||
mBiometricService.mInternalReceiver.onError(
|
||||
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
|
||||
BiometricAuthenticator.TYPE_FACE,
|
||||
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
|
||||
ERROR_TIMEOUT);
|
||||
0 /* vendorCode */);
|
||||
mBiometricService.mInternalReceiver.onDialogDismissed(
|
||||
BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
|
||||
waitForIdle();
|
||||
|
||||
verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
|
||||
verify(mBiometricService.mAuthenticators.get(0).impl,
|
||||
never()).cancelAuthenticationFromService(
|
||||
any(),
|
||||
any(),
|
||||
anyInt(),
|
||||
@@ -830,7 +825,8 @@ public class BiometricServiceTest {
|
||||
waitForIdle();
|
||||
|
||||
// doesn't send cancel to HAL
|
||||
verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
|
||||
verify(mBiometricService.mAuthenticators.get(0).impl,
|
||||
never()).cancelAuthenticationFromService(
|
||||
any(),
|
||||
any(),
|
||||
anyInt(),
|
||||
@@ -838,8 +834,9 @@ public class BiometricServiceTest {
|
||||
anyInt(),
|
||||
anyBoolean());
|
||||
verify(mReceiver1).onError(
|
||||
eq(BiometricAuthenticator.TYPE_FACE),
|
||||
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
|
||||
eq(ERROR_USER_CANCELED));
|
||||
eq(0 /* vendorCode */));
|
||||
assertNull(mBiometricService.mCurrentAuthSession);
|
||||
}
|
||||
|
||||
@@ -863,7 +860,7 @@ public class BiometricServiceTest {
|
||||
|
||||
// Helper methods
|
||||
|
||||
private void setupAuthForOnly(int modality) {
|
||||
private void setupAuthForOnly(int modality) throws RemoteException {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
|
||||
.thenReturn(false);
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
|
||||
@@ -871,17 +868,17 @@ public class BiometricServiceTest {
|
||||
if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
|
||||
.thenReturn(true);
|
||||
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
|
||||
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
|
||||
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
|
||||
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
|
||||
} else if (modality == BiometricAuthenticator.TYPE_FACE) {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
|
||||
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
|
||||
when(mFaceManager.isHardwareDetected()).thenReturn(true);
|
||||
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
|
||||
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
|
||||
} else {
|
||||
fail("Unknown modality: " + modality);
|
||||
}
|
||||
|
||||
mBiometricService = new BiometricService(mContext, new MockInjector());
|
||||
mBiometricService = new BiometricService(mContext, mInjector);
|
||||
mBiometricService.onStart();
|
||||
|
||||
when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
|
||||
|
||||
Reference in New Issue
Block a user