Merge changes from topic "faceCameraSensorPrivacy" into sc-v2-dev am: 698f40caf7

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16285463

Change-Id: I9982667e2035ad3d88a614b4ed5e8d5747c42ed4
This commit is contained in:
Joshua Mccloskey
2021-12-08 07:26:21 +00:00
committed by Automerger Merge Worker
14 changed files with 240 additions and 28 deletions

View File

@@ -150,6 +150,12 @@ public interface BiometricConstants {
*/ */
int BIOMETRIC_ERROR_RE_ENROLL = 16; int BIOMETRIC_ERROR_RE_ENROLL = 16;
/**
* The privacy setting has been enabled and will block use of the sensor.
* @hide
*/
int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18;
/** /**
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
* because the authentication attempt was unsuccessful. * because the authentication attempt was unsuccessful.

View File

@@ -69,7 +69,7 @@ public interface BiometricFaceConstants {
BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
BIOMETRIC_ERROR_RE_ENROLL, BIOMETRIC_ERROR_RE_ENROLL,
FACE_ERROR_UNKNOWN FACE_ERROR_UNKNOWN,
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@interface FaceError {} @interface FaceError {}

View File

@@ -1752,6 +1752,8 @@
<string name="face_setup_notification_title">Set up Face Unlock</string> <string name="face_setup_notification_title">Set up Face Unlock</string>
<!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] --> <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] -->
<string name="face_setup_notification_content">Unlock your phone by looking at it</string> <string name="face_setup_notification_content">Unlock your phone by looking at it</string>
<!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
<string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
<!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] --> <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
<string name="fingerprint_setup_notification_title">Set up more ways to unlock</string> <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string>
<!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] --> <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->

View File

@@ -2570,6 +2570,7 @@
<java-symbol type="string" name="face_recalibrate_notification_name" /> <java-symbol type="string" name="face_recalibrate_notification_name" />
<java-symbol type="string" name="face_recalibrate_notification_title" /> <java-symbol type="string" name="face_recalibrate_notification_title" />
<java-symbol type="string" name="face_recalibrate_notification_content" /> <java-symbol type="string" name="face_recalibrate_notification_content" />
<java-symbol type="string" name="face_sensor_privacy_enabled" />
<java-symbol type="string" name="face_error_unable_to_process" /> <java-symbol type="string" name="face_error_unable_to_process" />
<java-symbol type="string" name="face_error_hw_not_available" /> <java-symbol type="string" name="face_error_hw_not_available" />
<java-symbol type="string" name="face_error_no_space" /> <java-symbol type="string" name="face_error_no_space" />

View File

@@ -219,6 +219,9 @@
<!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] --> <!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] -->
<string name="kg_face_not_recognized">Not recognized</string> <string name="kg_face_not_recognized">Not recognized</string>
<!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
<string name="kg_face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
<!-- Instructions telling the user remaining times when enter SIM PIN view. --> <!-- Instructions telling the user remaining times when enter SIM PIN view. -->
<plurals name="kg_password_default_pin_message"> <plurals name="kg_password_default_pin_message">
<item quantity="one">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining <item quantity="one">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining

View File

@@ -51,6 +51,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -335,6 +336,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean mLockIconPressed; private boolean mLockIconPressed;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private final Executor mBackgroundExecutor; private final Executor mBackgroundExecutor;
private SensorPrivacyManager mSensorPrivacyManager;
private int mFaceAuthUserId;
/** /**
* Short delay before restarting fingerprint authentication after a successful try. This should * Short delay before restarting fingerprint authentication after a successful try. This should
@@ -1016,6 +1019,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// Error is always the end of authentication lifecycle // Error is always the end of authentication lifecycle
mFaceCancelSignal = null; mFaceCancelSignal = null;
boolean cameraPrivacyEnabled = false;
if (mSensorPrivacyManager != null) {
cameraPrivacyEnabled = mSensorPrivacyManager
.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
mFaceAuthUserId);
}
if (msgId == FaceManager.FACE_ERROR_CANCELED if (msgId == FaceManager.FACE_ERROR_CANCELED
&& mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) { && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
@@ -1025,7 +1034,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
setFaceRunningState(BIOMETRIC_STATE_STOPPED); setFaceRunningState(BIOMETRIC_STATE_STOPPED);
} }
if (msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE final boolean isHwUnavailable = msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE;
if (isHwUnavailable
|| msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) { || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) {
if (mHardwareFaceUnavailableRetryCount < HAL_ERROR_RETRY_MAX) { if (mHardwareFaceUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
mHardwareFaceUnavailableRetryCount++; mHardwareFaceUnavailableRetryCount++;
@@ -1041,6 +1052,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
requireStrongAuthIfAllLockedOut(); requireStrongAuthIfAllLockedOut();
} }
if (isHwUnavailable && cameraPrivacyEnabled) {
errString = mContext.getString(R.string.kg_face_sensor_privacy_enabled);
}
for (int i = 0; i < mCallbacks.size(); i++) { for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) { if (cb != null) {
@@ -1816,6 +1831,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mLockPatternUtils = lockPatternUtils; mLockPatternUtils = lockPatternUtils;
mAuthController = authController; mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this); dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
mHandler = new Handler(mainLooper) { mHandler = new Handler(mainLooper) {
@Override @Override
@@ -2517,6 +2533,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// This would need to be updated for multi-sensor devices // This would need to be updated for multi-sensor devices
final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty() final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
&& mFaceSensorProperties.get(0).supportsFaceDetection; && mFaceSensorProperties.get(0).supportsFaceDetection;
mFaceAuthUserId = userId;
if (isEncryptedOrLockdown(userId) && supportsFaceDetection) { if (isEncryptedOrLockdown(userId) && supportsFaceDetection) {
mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId); mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
} else { } else {

View File

@@ -31,6 +31,7 @@ import android.content.IntentFilter;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.PointF; import android.graphics.PointF;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -89,6 +90,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
private static final String TAG = "AuthController"; private static final String TAG = "AuthController";
private static final boolean DEBUG = true; private static final boolean DEBUG = true;
private static final int SENSOR_PRIVACY_DELAY = 500;
private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Handler mHandler = new Handler(Looper.getMainLooper());
private final CommandQueue mCommandQueue; private final CommandQueue mCommandQueue;
@@ -122,6 +124,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
private SensorPrivacyManager mSensorPrivacyManager;
private class BiometricTaskStackListener extends TaskStackListener { private class BiometricTaskStackListener extends TaskStackListener {
@Override @Override
@@ -492,6 +495,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mBroadcastReceiver, filter); context.registerReceiver(mBroadcastReceiver, filter);
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
} }
private void updateFingerprintLocation() { private void updateFingerprintLocation() {
@@ -642,10 +646,16 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT) final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT); || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
boolean isCameraPrivacyEnabled = false;
if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE
&& mSensorPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
mCurrentDialogArgs.argi1 /* userId */)) {
isCameraPrivacyEnabled = true;
}
// TODO(b/141025588): Create separate methods for handling hard and soft errors. // TODO(b/141025588): Create separate methods for handling hard and soft errors.
final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
|| error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT); || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT
|| isCameraPrivacyEnabled);
if (mCurrentDialog != null) { if (mCurrentDialog != null) {
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) { if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
if (DEBUG) Log.d(TAG, "onBiometricError, lockout"); if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
@@ -655,12 +665,23 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
? mContext.getString(R.string.biometric_not_recognized) ? mContext.getString(R.string.biometric_not_recognized)
: getErrorString(modality, error, vendorCode); : getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage); if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
// The camera privacy error can return before the prompt initializes its state,
// causing the prompt to appear to endlessly authenticate. Add a small delay
// to stop this.
if (isCameraPrivacyEnabled) {
mHandler.postDelayed(() -> {
mCurrentDialog.onAuthenticationFailed(modality,
mContext.getString(R.string.face_sensor_privacy_enabled));
}, SENSOR_PRIVACY_DELAY);
} else {
mCurrentDialog.onAuthenticationFailed(modality, errorMessage); mCurrentDialog.onAuthenticationFailed(modality, errorMessage);
}
} else { } else {
final String errorMessage = getErrorString(modality, error, vendorCode); final String errorMessage = getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage); if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
mCurrentDialog.onError(modality, errorMessage); mCurrentDialog.onError(modality, errorMessage);
} }
} else { } else {
Log.w(TAG, "onBiometricError callback but dialog is gone"); Log.w(TAG, "onBiometricError callback but dialog is gone");
} }

View File

@@ -1036,7 +1036,8 @@ public class BiometricService extends SystemService {
promptInfo.setAuthenticators(authenticators); promptInfo.setAuthenticators(authenticators);
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */); userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
getContext());
} }
/** /**
@@ -1375,7 +1376,8 @@ public class BiometricService extends SystemService {
try { try {
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo, mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists()); opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
getContext());
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus(); final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
@@ -1383,8 +1385,11 @@ public class BiometricService extends SystemService {
+ "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo
+ " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: " + " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: "
+ promptInfo.isIgnoreEnrollmentState()); + promptInfo.isIgnoreEnrollmentState());
// BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) { // be shown for this case.
if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
|| preAuthStatus.second
== BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
// If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
// CREDENTIAL is requested and available, set the bundle to only request // CREDENTIAL is requested and available, set the bundle to only request
// CREDENTIAL. // CREDENTIAL.

View File

@@ -26,6 +26,8 @@ import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager; import android.app.trust.ITrustManager;
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.PromptInfo;
@@ -59,6 +61,7 @@ class PreAuthInfo {
static final int CREDENTIAL_NOT_ENROLLED = 9; static final int CREDENTIAL_NOT_ENROLLED = 9;
static final int BIOMETRIC_LOCKOUT_TIMED = 10; static final int BIOMETRIC_LOCKOUT_TIMED = 10;
static final int BIOMETRIC_LOCKOUT_PERMANENT = 11; static final int BIOMETRIC_LOCKOUT_PERMANENT = 11;
static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12;
@IntDef({AUTHENTICATOR_OK, @IntDef({AUTHENTICATOR_OK,
BIOMETRIC_NO_HARDWARE, BIOMETRIC_NO_HARDWARE,
BIOMETRIC_DISABLED_BY_DEVICE_POLICY, BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
@@ -69,7 +72,8 @@ class PreAuthInfo {
BIOMETRIC_NOT_ENABLED_FOR_APPS, BIOMETRIC_NOT_ENABLED_FOR_APPS,
CREDENTIAL_NOT_ENROLLED, CREDENTIAL_NOT_ENROLLED,
BIOMETRIC_LOCKOUT_TIMED, BIOMETRIC_LOCKOUT_TIMED,
BIOMETRIC_LOCKOUT_PERMANENT}) BIOMETRIC_LOCKOUT_PERMANENT,
BIOMETRIC_SENSOR_PRIVACY_ENABLED})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@interface AuthenticatorStatus {} @interface AuthenticatorStatus {}
@@ -84,13 +88,15 @@ class PreAuthInfo {
final boolean credentialAvailable; final boolean credentialAvailable;
final boolean confirmationRequested; final boolean confirmationRequested;
final boolean ignoreEnrollmentState; final boolean ignoreEnrollmentState;
final int userId;
final Context context;
static PreAuthInfo create(ITrustManager trustManager, static PreAuthInfo create(ITrustManager trustManager,
DevicePolicyManager devicePolicyManager, DevicePolicyManager devicePolicyManager,
BiometricService.SettingObserver settingObserver, BiometricService.SettingObserver settingObserver,
List<BiometricSensor> sensors, List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName, int userId, PromptInfo promptInfo, String opPackageName,
boolean checkDevicePolicyManager) boolean checkDevicePolicyManager, Context context)
throws RemoteException { throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested(); final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -116,14 +122,22 @@ class PreAuthInfo {
devicePolicyManager, settingObserver, sensor, userId, opPackageName, devicePolicyManager, settingObserver, sensor, userId, opPackageName,
checkDevicePolicyManager, requestedStrength, checkDevicePolicyManager, requestedStrength,
promptInfo.getAllowedSensorIds(), promptInfo.getAllowedSensorIds(),
promptInfo.isIgnoreEnrollmentState()); promptInfo.isIgnoreEnrollmentState(),
context);
Slog.d(TAG, "Package: " + opPackageName Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id + " Sensor ID: " + sensor.id
+ " Modality: " + sensor.modality + " Modality: " + sensor.modality
+ " Status: " + status); + " Status: " + status);
if (status == AUTHENTICATOR_OK) { // A sensor with privacy enabled will still be eligible to
// authenticate with biometric prompt. This is so the framework can display
// a sensor privacy error message to users after briefly showing the
// Biometric Prompt.
//
// Note: if only a certain sensor is required and the privacy is enabled,
// canAuthenticate() will return false.
if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
eligibleSensors.add(sensor); eligibleSensors.add(sensor);
} else { } else {
ineligibleSensors.add(new Pair<>(sensor, status)); ineligibleSensors.add(new Pair<>(sensor, status));
@@ -133,7 +147,7 @@ class PreAuthInfo {
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
promptInfo.isIgnoreEnrollmentState()); promptInfo.isIgnoreEnrollmentState(), userId, context);
} }
/** /**
@@ -149,7 +163,7 @@ class PreAuthInfo {
BiometricSensor sensor, int userId, String opPackageName, BiometricSensor sensor, int userId, String opPackageName,
boolean checkDevicePolicyManager, int requestedStrength, boolean checkDevicePolicyManager, int requestedStrength,
@NonNull List<Integer> requestedSensorIds, @NonNull List<Integer> requestedSensorIds,
boolean ignoreEnrollmentState) { boolean ignoreEnrollmentState, Context context) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) { if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE; return BIOMETRIC_NO_HARDWARE;
@@ -175,6 +189,16 @@ class PreAuthInfo {
&& !ignoreEnrollmentState) { && !ignoreEnrollmentState) {
return BIOMETRIC_NOT_ENROLLED; return BIOMETRIC_NOT_ENROLLED;
} }
final SensorPrivacyManager sensorPrivacyManager = context
.getSystemService(SensorPrivacyManager.class);
if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
if (sensorPrivacyManager
.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
}
}
final @LockoutTracker.LockoutMode int lockoutMode = final @LockoutTracker.LockoutMode int lockoutMode =
sensor.impl.getLockoutModeForUser(userId); sensor.impl.getLockoutModeForUser(userId);
@@ -243,7 +267,8 @@ class PreAuthInfo {
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors, boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
boolean confirmationRequested, boolean ignoreEnrollmentState) { boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
Context context) {
mBiometricRequested = biometricRequested; mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested; mBiometricStrengthRequested = biometricStrengthRequested;
this.credentialRequested = credentialRequested; this.credentialRequested = credentialRequested;
@@ -253,6 +278,8 @@ class PreAuthInfo {
this.credentialAvailable = credentialAvailable; this.credentialAvailable = credentialAvailable;
this.confirmationRequested = confirmationRequested; this.confirmationRequested = confirmationRequested;
this.ignoreEnrollmentState = ignoreEnrollmentState; this.ignoreEnrollmentState = ignoreEnrollmentState;
this.userId = userId;
this.context = context;
} }
private Pair<BiometricSensor, Integer> calculateErrorByPriority() { private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
@@ -280,15 +307,35 @@ class PreAuthInfo {
private Pair<Integer, Integer> getInternalStatus() { private Pair<Integer, Integer> getInternalStatus() {
@AuthenticatorStatus final int status; @AuthenticatorStatus final int status;
@BiometricAuthenticator.Modality int modality = TYPE_NONE; @BiometricAuthenticator.Modality int modality = TYPE_NONE;
final SensorPrivacyManager sensorPrivacyManager = context
.getSystemService(SensorPrivacyManager.class);
boolean cameraPrivacyEnabled = false;
if (sensorPrivacyManager != null) {
cameraPrivacyEnabled = sensorPrivacyManager
.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
}
if (mBiometricRequested && credentialRequested) { if (mBiometricRequested && credentialRequested) {
if (credentialAvailable || !eligibleSensors.isEmpty()) { if (credentialAvailable || !eligibleSensors.isEmpty()) {
status = AUTHENTICATOR_OK;
if (credentialAvailable) {
modality |= TYPE_CREDENTIAL;
}
for (BiometricSensor sensor : eligibleSensors) { for (BiometricSensor sensor : eligibleSensors) {
modality |= sensor.modality; modality |= sensor.modality;
} }
if (credentialAvailable) {
modality |= TYPE_CREDENTIAL;
status = AUTHENTICATOR_OK;
} else if (modality == TYPE_FACE && cameraPrivacyEnabled) {
// If the only modality requested is face, credential is unavailable,
// and the face sensor privacy is enabled then return
// BIOMETRIC_SENSOR_PRIVACY_ENABLED.
//
// Note: This sensor will still be eligible for calls to authenticate.
status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
} else {
status = AUTHENTICATOR_OK;
}
} else { } else {
// Pick the first sensor error if it exists // Pick the first sensor error if it exists
if (!ineligibleSensors.isEmpty()) { if (!ineligibleSensors.isEmpty()) {
@@ -302,10 +349,18 @@ class PreAuthInfo {
} }
} else if (mBiometricRequested) { } else if (mBiometricRequested) {
if (!eligibleSensors.isEmpty()) { if (!eligibleSensors.isEmpty()) {
status = AUTHENTICATOR_OK;
for (BiometricSensor sensor : eligibleSensors) { for (BiometricSensor sensor : eligibleSensors) {
modality |= sensor.modality; modality |= sensor.modality;
} }
if (modality == TYPE_FACE && cameraPrivacyEnabled) {
// If the only modality requested is face and the privacy is enabled
// then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
//
// Note: This sensor will still be eligible for calls to authenticate.
status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
} else {
status = AUTHENTICATOR_OK;
}
} else { } else {
// Pick the first sensor error if it exists // Pick the first sensor error if it exists
if (!ineligibleSensors.isEmpty()) { if (!ineligibleSensors.isEmpty()) {
@@ -326,9 +381,9 @@ class PreAuthInfo {
Slog.e(TAG, "No authenticators requested"); Slog.e(TAG, "No authenticators requested");
status = BIOMETRIC_NO_HARDWARE; status = BIOMETRIC_NO_HARDWARE;
} }
Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality
+ " AuthenticatorStatus: " + status); + " AuthenticatorStatus: " + status);
return new Pair<>(modality, status); return new Pair<>(modality, status);
} }
@@ -362,6 +417,7 @@ class PreAuthInfo {
case CREDENTIAL_NOT_ENROLLED: case CREDENTIAL_NOT_ENROLLED:
case BIOMETRIC_LOCKOUT_TIMED: case BIOMETRIC_LOCKOUT_TIMED:
case BIOMETRIC_LOCKOUT_PERMANENT: case BIOMETRIC_LOCKOUT_PERMANENT:
case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
break; break;
case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:

View File

@@ -33,6 +33,7 @@ import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_LOCKOUT_TIMED;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED;
import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED; import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED;
import android.annotation.NonNull; import android.annotation.NonNull;
@@ -278,6 +279,9 @@ public class Utils {
case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT: case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS; biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS;
break; break;
case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED:
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
break;
default: default:
Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
@@ -337,7 +341,8 @@ public class Utils {
case BIOMETRIC_LOCKOUT_PERMANENT: case BIOMETRIC_LOCKOUT_PERMANENT:
return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED;
case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
case BIOMETRIC_HARDWARE_NOT_DETECTED: case BIOMETRIC_HARDWARE_NOT_DETECTED:
case BIOMETRIC_NOT_ENABLED_FOR_APPS: case BIOMETRIC_NOT_ENABLED_FOR_APPS:

View File

@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricFaceConstants;
@@ -56,6 +57,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
@NonNull private final LockoutCache mLockoutCache; @NonNull private final LockoutCache mLockoutCache;
@Nullable private final NotificationManager mNotificationManager; @Nullable private final NotificationManager mNotificationManager;
@Nullable private ICancellationSignal mCancellationSignal; @Nullable private ICancellationSignal mCancellationSignal;
@Nullable private SensorPrivacyManager mSensorPrivacyManager;
private final int[] mBiometricPromptIgnoreList; private final int[] mBiometricPromptIgnoreList;
private final int[] mBiometricPromptIgnoreListVendor; private final int[] mBiometricPromptIgnoreListVendor;
@@ -81,6 +83,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
mUsageStats = usageStats; mUsageStats = usageStats;
mLockoutCache = lockoutCache; mLockoutCache = lockoutCache;
mNotificationManager = context.getSystemService(NotificationManager.class); mNotificationManager = context.getSystemService(NotificationManager.class);
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
final Resources resources = getContext().getResources(); final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray( mBiometricPromptIgnoreList = resources.getIntArray(
@@ -108,7 +111,16 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
@Override @Override
protected void startHalOperation() { protected void startHalOperation() {
try { try {
if (mSensorPrivacyManager != null
&& mSensorPrivacyManager
.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
getTargetUserId())) {
onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
} else {
mCancellationSignal = getFreshDaemon().authenticate(mOperationId); mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
}
} catch (RemoteException e) { } catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e); Slog.e(TAG, "Remote exception when requesting auth", e);
onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);

View File

@@ -19,6 +19,8 @@ package com.android.server.biometrics.sensors.face.aidl;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.face.ISession; import android.hardware.biometrics.face.ISession;
@@ -41,6 +43,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det
private final boolean mIsStrongBiometric; private final boolean mIsStrongBiometric;
@Nullable private ICancellationSignal mCancellationSignal; @Nullable private ICancellationSignal mCancellationSignal;
@Nullable private SensorPrivacyManager mSensorPrivacyManager;
public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
@NonNull IBinder token, long requestId, @NonNull IBinder token, long requestId,
@@ -51,6 +54,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
setRequestId(requestId); setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric; mIsStrongBiometric = isStrongBiometric;
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
} }
@Override @Override
@@ -73,6 +77,14 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det
@Override @Override
protected void startHalOperation() { protected void startHalOperation() {
if (mSensorPrivacyManager != null
&& mSensorPrivacyManager
.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) {
onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
return;
}
try { try {
mCancellationSignal = getFreshDaemon().detectInteraction(); mCancellationSignal = getFreshDaemon().detectInteraction();
} catch (RemoteException e) { } catch (RemoteException e) {

View File

@@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.face.hidl;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricFaceConstants;
@@ -55,6 +56,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
private final int[] mKeyguardIgnoreListVendor; private final int[] mKeyguardIgnoreListVendor;
private int mLastAcquire; private int mLastAcquire;
private SensorPrivacyManager mSensorPrivacyManager;
FaceAuthenticationClient(@NonNull Context context, FaceAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
@@ -71,6 +73,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
isKeyguardBypassEnabled); isKeyguardBypassEnabled);
setRequestId(requestId); setRequestId(requestId);
mUsageStats = usageStats; mUsageStats = usageStats;
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
final Resources resources = getContext().getResources(); final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray( mBiometricPromptIgnoreList = resources.getIntArray(
@@ -97,6 +100,15 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
@Override @Override
protected void startHalOperation() { protected void startHalOperation() {
if (mSensorPrivacyManager != null
&& mSensorPrivacyManager
.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) {
onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
return;
}
try { try {
getFreshDaemon().authenticate(mOperationId); getFreshDaemon().authenticate(mOperationId);
} catch (RemoteException e) { } catch (RemoteException e) {

View File

@@ -302,6 +302,65 @@ public class AuthSessionTest {
testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null)); testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
} }
// TODO (b/208484275) : Enable these tests
// @Test
// public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
// SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
// when(manager
// .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
// .thenReturn(false);
// when(mContext.getSystemService(SensorPrivacyManager.class))
// .thenReturn(manager);
// setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
// mock(IBiometricAuthenticator.class));
// final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG);
// final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
// assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult());
// for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
// assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
// }
// }
// @Test
// public void testPreAuth_cannotAuthAndPrivacyEnabled() throws Exception {
// SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
// when(manager
// .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
// .thenReturn(true);
// when(mContext.getSystemService(SensorPrivacyManager.class))
// .thenReturn(manager);
// setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
// mock(IBiometricAuthenticator.class));
// final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG);
// final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
// assertEquals(BiometricManager.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED,
// preAuthInfo.getCanAuthenticateResult());
// // Even though canAuth returns privacy enabled, we should still be able to authenticate.
// for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
// assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
// }
// }
// @Test
// public void testPreAuth_canAuthAndPrivacyEnabledCredentialEnabled() throws Exception {
// SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
// when(manager
// .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
// .thenReturn(true);
// when(mContext.getSystemService(SensorPrivacyManager.class))
// .thenReturn(manager);
// setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
// mock(IBiometricAuthenticator.class));
// final PromptInfo promptInfo =
// createPromptInfo(Authenticators.BIOMETRIC_STRONG
// | Authenticators. DEVICE_CREDENTIAL);
// final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
// assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult());
// for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
// assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
// }
// }
private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException { private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException {
final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class); final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
@@ -331,7 +390,8 @@ public class AuthSessionTest {
userId, userId,
promptInfo, promptInfo,
TEST_PACKAGE, TEST_PACKAGE,
checkDevicePolicyManager); checkDevicePolicyManager,
mContext);
} }
private AuthSession createAuthSession(List<BiometricSensor> sensors, private AuthSession createAuthSession(List<BiometricSensor> sensors,