Merge "Face auth not eligible if camera in use" into udc-d1-dev

This commit is contained in:
Diya Bera
2023-07-13 21:04:20 +00:00
committed by Android (Google) Code Review
8 changed files with 141 additions and 67 deletions

View File

@@ -17,9 +17,16 @@
package com.android.server.biometrics; package com.android.server.biometrics;
/** /**
* Interface for biometric operations to get camera privacy state. * Interface for biometrics to get camera status.
*/ */
public interface BiometricSensorPrivacy { public interface BiometricCameraManager {
/* Returns true if privacy is enabled and camera access is disabled. */ /**
* Returns true if any camera is in use.
*/
boolean isAnyCameraUnavailable();
/**
* Returns true if privacy is enabled and camera access is disabled.
*/
boolean isCameraPrivacyEnabled(); boolean isCameraPrivacyEnabled();
} }

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 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;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import android.annotation.NonNull;
import android.hardware.SensorPrivacyManager;
import android.hardware.camera2.CameraManager;
import java.util.concurrent.ConcurrentHashMap;
public class BiometricCameraManagerImpl implements BiometricCameraManager {
private final CameraManager mCameraManager;
private final SensorPrivacyManager mSensorPrivacyManager;
private final ConcurrentHashMap<String, Boolean> mIsCameraAvailable = new ConcurrentHashMap<>();
private final CameraManager.AvailabilityCallback mCameraAvailabilityCallback =
new CameraManager.AvailabilityCallback() {
@Override
public void onCameraAvailable(@NonNull String cameraId) {
mIsCameraAvailable.put(cameraId, true);
}
@Override
public void onCameraUnavailable(@NonNull String cameraId) {
mIsCameraAvailable.put(cameraId, false);
}
};
public BiometricCameraManagerImpl(@NonNull CameraManager cameraManager,
@NonNull SensorPrivacyManager sensorPrivacyManager) {
mCameraManager = cameraManager;
mSensorPrivacyManager = sensorPrivacyManager;
mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, null);
}
@Override
public boolean isAnyCameraUnavailable() {
for (String cameraId : mIsCameraAvailable.keySet()) {
if (!mIsCameraAvailable.get(cameraId)) {
return true;
}
}
return false;
}
@Override
public boolean isCameraPrivacyEnabled() {
return mSensorPrivacyManager != null && mSensorPrivacyManager
.isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright (C) 2023 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;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import android.annotation.Nullable;
import android.hardware.SensorPrivacyManager;
public class BiometricSensorPrivacyImpl implements
BiometricSensorPrivacy {
private final SensorPrivacyManager mSensorPrivacyManager;
public BiometricSensorPrivacyImpl(@Nullable SensorPrivacyManager sensorPrivacyManager) {
mSensorPrivacyManager = sensorPrivacyManager;
}
@Override
public boolean isCameraPrivacyEnabled() {
return mSensorPrivacyManager != null && mSensorPrivacyManager
.isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
}
}

View File

@@ -48,6 +48,7 @@ import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.camera2.CameraManager;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.net.Uri; import android.net.Uri;
@@ -125,7 +126,7 @@ public class BiometricService extends SystemService {
AuthSession mAuthSession; AuthSession mAuthSession;
private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Handler mHandler = new Handler(Looper.getMainLooper());
private final BiometricSensorPrivacy mBiometricSensorPrivacy; private final BiometricCameraManager mBiometricCameraManager;
/** /**
* Tracks authenticatorId invalidation. For more details, see * Tracks authenticatorId invalidation. For more details, see
@@ -936,7 +937,7 @@ public class BiometricService extends SystemService {
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(), mBiometricSensorPrivacy); getContext(), mBiometricCameraManager);
} }
/** /**
@@ -1030,9 +1031,9 @@ public class BiometricService extends SystemService {
return context.getSystemService(UserManager.class); return context.getSystemService(UserManager.class);
} }
public BiometricSensorPrivacy getBiometricSensorPrivacy(Context context) { public BiometricCameraManager getBiometricCameraManager(Context context) {
return new BiometricSensorPrivacyImpl(context.getSystemService( return new BiometricCameraManagerImpl(context.getSystemService(CameraManager.class),
SensorPrivacyManager.class)); context.getSystemService(SensorPrivacyManager.class));
} }
} }
@@ -1062,7 +1063,7 @@ public class BiometricService extends SystemService {
mRequestCounter = mInjector.getRequestGenerator(); mRequestCounter = mInjector.getRequestGenerator();
mBiometricContext = injector.getBiometricContext(context); mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context); mUserManager = injector.getUserManager(context);
mBiometricSensorPrivacy = injector.getBiometricSensorPrivacy(context); mBiometricCameraManager = injector.getBiometricCameraManager(context);
try { try {
injector.getActivityManagerService().registerUserSwitchObserver( injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1299,7 +1300,7 @@ public class BiometricService extends SystemService {
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(), mBiometricSensorPrivacy); getContext(), mBiometricCameraManager);
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus(); final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();

View File

@@ -72,16 +72,16 @@ class PreAuthInfo {
final Context context; final Context context;
private final boolean mBiometricRequested; private final boolean mBiometricRequested;
private final int mBiometricStrengthRequested; private final int mBiometricStrengthRequested;
private final BiometricSensorPrivacy mBiometricSensorPrivacy; private final BiometricCameraManager mBiometricCameraManager;
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, int userId, boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
Context context, BiometricSensorPrivacy biometricSensorPrivacy) { Context context, BiometricCameraManager biometricCameraManager) {
mBiometricRequested = biometricRequested; mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested; mBiometricStrengthRequested = biometricStrengthRequested;
mBiometricSensorPrivacy = biometricSensorPrivacy; mBiometricCameraManager = biometricCameraManager;
this.credentialRequested = credentialRequested; this.credentialRequested = credentialRequested;
this.eligibleSensors = eligibleSensors; this.eligibleSensors = eligibleSensors;
@@ -99,7 +99,7 @@ class PreAuthInfo {
List<BiometricSensor> sensors, List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName, int userId, PromptInfo promptInfo, String opPackageName,
boolean checkDevicePolicyManager, Context context, boolean checkDevicePolicyManager, Context context,
BiometricSensorPrivacy biometricSensorPrivacy) BiometricCameraManager biometricCameraManager)
throws RemoteException { throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested(); final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -127,7 +127,7 @@ class PreAuthInfo {
checkDevicePolicyManager, requestedStrength, checkDevicePolicyManager, requestedStrength,
promptInfo.getAllowedSensorIds(), promptInfo.getAllowedSensorIds(),
promptInfo.isIgnoreEnrollmentState(), promptInfo.isIgnoreEnrollmentState(),
biometricSensorPrivacy); biometricCameraManager);
Slog.d(TAG, "Package: " + opPackageName Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id + " Sensor ID: " + sensor.id
@@ -151,7 +151,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(), userId, context, biometricSensorPrivacy); promptInfo.isIgnoreEnrollmentState(), userId, context, biometricCameraManager);
} }
/** /**
@@ -168,12 +168,16 @@ 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, BiometricSensorPrivacy biometricSensorPrivacy) { boolean ignoreEnrollmentState, BiometricCameraManager biometricCameraManager) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) { if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE; return BIOMETRIC_NO_HARDWARE;
} }
if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
return BIOMETRIC_HARDWARE_NOT_DETECTED;
}
final boolean wasStrongEnough = final boolean wasStrongEnough =
Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength); Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
final boolean isStrongEnough = final boolean isStrongEnough =
@@ -195,8 +199,8 @@ class PreAuthInfo {
return BIOMETRIC_NOT_ENROLLED; return BIOMETRIC_NOT_ENROLLED;
} }
if (biometricSensorPrivacy != null && sensor.modality == TYPE_FACE) { if (biometricCameraManager != null && sensor.modality == TYPE_FACE) {
if (biometricSensorPrivacy.isCameraPrivacyEnabled()) { if (biometricCameraManager.isCameraPrivacyEnabled()) {
//Camera privacy is enabled as the access is disabled //Camera privacy is enabled as the access is disabled
return BIOMETRIC_SENSOR_PRIVACY_ENABLED; return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
} }
@@ -307,8 +311,8 @@ class PreAuthInfo {
@BiometricAuthenticator.Modality int modality = TYPE_NONE; @BiometricAuthenticator.Modality int modality = TYPE_NONE;
boolean cameraPrivacyEnabled = false; boolean cameraPrivacyEnabled = false;
if (mBiometricSensorPrivacy != null) { if (mBiometricCameraManager != null) {
cameraPrivacyEnabled = mBiometricSensorPrivacy.isCameraPrivacyEnabled(); cameraPrivacyEnabled = mBiometricCameraManager.isCameraPrivacyEnabled();
} }
if (mBiometricRequested && credentialRequested) { if (mBiometricRequested && credentialRequested) {

View File

@@ -106,7 +106,7 @@ public class AuthSessionTest {
@Mock private KeyStore mKeyStore; @Mock private KeyStore mKeyStore;
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver; @Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger; @Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
@Mock BiometricSensorPrivacy mBiometricSensorPrivacy; @Mock private BiometricCameraManager mBiometricCameraManager;
private Random mRandom; private Random mRandom;
private IBinder mToken; private IBinder mToken;
@@ -609,7 +609,7 @@ public class AuthSessionTest {
TEST_PACKAGE, TEST_PACKAGE,
checkDevicePolicyManager, checkDevicePolicyManager,
mContext, mContext,
mBiometricSensorPrivacy); mBiometricCameraManager);
} }
private AuthSession createAuthSession(List<BiometricSensor> sensors, private AuthSession createAuthSession(List<BiometricSensor> sensors,

View File

@@ -151,6 +151,8 @@ public class BiometricServiceTest {
private AuthSessionCoordinator mAuthSessionCoordinator; private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock @Mock
private UserManager mUserManager; private UserManager mUserManager;
@Mock
private BiometricCameraManager mBiometricCameraManager;
BiometricContextProvider mBiometricContextProvider; BiometricContextProvider mBiometricContextProvider;
@@ -177,6 +179,7 @@ public class BiometricServiceTest {
when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager); when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID); when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID);
when(mInjector.getUserManager(any())).thenReturn(mUserManager); when(mInjector.getUserManager(any())).thenReturn(mUserManager);
when(mInjector.getBiometricCameraManager(any())).thenReturn(mBiometricCameraManager);
when(mResources.getString(R.string.biometric_error_hw_unavailable)) when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE); .thenReturn(ERROR_HW_UNAVAILABLE);

View File

@@ -67,7 +67,7 @@ public class PreAuthInfoTest {
@Mock @Mock
BiometricService.SettingObserver mSettingObserver; BiometricService.SettingObserver mSettingObserver;
@Mock @Mock
BiometricSensorPrivacy mBiometricSensorPrivacyUtil; BiometricCameraManager mBiometricCameraManager;
@Before @Before
public void setup() throws RemoteException { public void setup() throws RemoteException {
@@ -79,11 +79,13 @@ public class PreAuthInfoTest {
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true); when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt())) when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(LOCKOUT_NONE); .thenReturn(LOCKOUT_NONE);
when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(false);
when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(false);
} }
@Test @Test
public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception { public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception {
when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(true); when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(true);
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE, BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) { BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
@@ -104,15 +106,14 @@ public class PreAuthInfoTest {
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
mSettingObserver, List.of(sensor), mSettingObserver, List.of(sensor),
0 /* userId */, promptInfo, TEST_PACKAGE_NAME, 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil); false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
assertThat(preAuthInfo.eligibleSensors).isEmpty(); assertThat(preAuthInfo.eligibleSensors).isEmpty();
} }
@Test @Test
public void testFaceAuthentication_whenCameraPrivacyIsDisabled() throws Exception { public void testFaceAuthentication_whenCameraPrivacyIsDisabledAndCameraIsAvailable()
when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(false); throws Exception {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE, BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) { BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
@Override @Override
@@ -132,8 +133,35 @@ public class PreAuthInfoTest {
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
mSettingObserver, List.of(sensor), mSettingObserver, List.of(sensor),
0 /* userId */, promptInfo, TEST_PACKAGE_NAME, 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil); false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1); assertThat(preAuthInfo.eligibleSensors).hasSize(1);
} }
@Test
public void testFaceAuthentication_whenCameraIsUnavailable() throws RemoteException {
when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(true);
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
return false;
}
@Override
boolean confirmationSupported() {
return false;
}
};
PromptInfo promptInfo = new PromptInfo();
promptInfo.setConfirmationRequested(false /* requireConfirmation */);
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
mSettingObserver, List.of(sensor),
0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(0);
}
} }