Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c35a712a authored by Diya Bera's avatar Diya Bera
Browse files

Make face auth ineligible when camera access is disabled

Test: Disable camera access in Settings, Open biometric prompt test app
and try to authenticate; Fingerprint authentication should work normally
Bug: 277854521

Change-Id: I19556fa7f861f2f7655e792b1da9d74d3cf2259e
parent eb090205
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
/*
 * 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;

/**
 * Interface for biometric operations to get camera privacy state.
 */
public interface BiometricSensorPrivacy {
    /* Returns true if privacy is enabled and camera access is disabled. */
    boolean isCameraPrivacyEnabled();
}
+37 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+12 −5
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
@@ -124,6 +125,8 @@ public class BiometricService extends SystemService {
    AuthSession mAuthSession;
    private final Handler mHandler = new Handler(Looper.getMainLooper());

    private final BiometricSensorPrivacy mBiometricSensorPrivacy;

    /**
     * Tracks authenticatorId invalidation. For more details, see
     * {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}.
@@ -933,7 +936,7 @@ public class BiometricService extends SystemService {

        return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
                userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
                getContext());
                getContext(), mBiometricSensorPrivacy);
    }

    /**
@@ -1026,6 +1029,11 @@ public class BiometricService extends SystemService {
        public UserManager getUserManager(Context context) {
            return context.getSystemService(UserManager.class);
        }

        public BiometricSensorPrivacy getBiometricSensorPrivacy(Context context) {
            return new BiometricSensorPrivacyImpl(context.getSystemService(
                    SensorPrivacyManager.class));
        }
    }

    /**
@@ -1054,6 +1062,7 @@ public class BiometricService extends SystemService {
        mRequestCounter = mInjector.getRequestGenerator();
        mBiometricContext = injector.getBiometricContext(context);
        mUserManager = injector.getUserManager(context);
        mBiometricSensorPrivacy = injector.getBiometricSensorPrivacy(context);

        try {
            injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1290,7 +1299,7 @@ public class BiometricService extends SystemService {
                final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
                        mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
                        opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
                        getContext());
                        getContext(), mBiometricSensorPrivacy);

                final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();

@@ -1300,9 +1309,7 @@ public class BiometricService extends SystemService {
                        + promptInfo.isIgnoreEnrollmentState());
                // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
                // be shown for this case.
                if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
                        || preAuthStatus.second
                        == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
                if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
                    // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
                    // CREDENTIAL is requested and available, set the bundle to only request
                    // CREDENTIAL.
+17 −20
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.PromptInfo;
@@ -73,13 +72,16 @@ class PreAuthInfo {
    final Context context;
    private final boolean mBiometricRequested;
    private final int mBiometricStrengthRequested;
    private final BiometricSensorPrivacy mBiometricSensorPrivacy;

    private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
            boolean credentialRequested, List<BiometricSensor> eligibleSensors,
            List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
            boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
            Context context) {
            Context context, BiometricSensorPrivacy biometricSensorPrivacy) {
        mBiometricRequested = biometricRequested;
        mBiometricStrengthRequested = biometricStrengthRequested;
        mBiometricSensorPrivacy = biometricSensorPrivacy;
        this.credentialRequested = credentialRequested;

        this.eligibleSensors = eligibleSensors;
@@ -96,7 +98,8 @@ class PreAuthInfo {
            BiometricService.SettingObserver settingObserver,
            List<BiometricSensor> sensors,
            int userId, PromptInfo promptInfo, String opPackageName,
            boolean checkDevicePolicyManager, Context context)
            boolean checkDevicePolicyManager, Context context,
            BiometricSensorPrivacy biometricSensorPrivacy)
            throws RemoteException {

        final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -124,7 +127,7 @@ class PreAuthInfo {
                        checkDevicePolicyManager, requestedStrength,
                        promptInfo.getAllowedSensorIds(),
                        promptInfo.isIgnoreEnrollmentState(),
                        context);
                        biometricSensorPrivacy);

                Slog.d(TAG, "Package: " + opPackageName
                        + " Sensor ID: " + sensor.id
@@ -138,7 +141,7 @@ class PreAuthInfo {
                //
                // 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) {
                if (status == AUTHENTICATOR_OK) {
                    eligibleSensors.add(sensor);
                } else {
                    ineligibleSensors.add(new Pair<>(sensor, status));
@@ -148,7 +151,7 @@ class PreAuthInfo {

        return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
                eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
                promptInfo.isIgnoreEnrollmentState(), userId, context);
                promptInfo.isIgnoreEnrollmentState(), userId, context, biometricSensorPrivacy);
    }

    /**
@@ -165,7 +168,7 @@ class PreAuthInfo {
            BiometricSensor sensor, int userId, String opPackageName,
            boolean checkDevicePolicyManager, int requestedStrength,
            @NonNull List<Integer> requestedSensorIds,
            boolean ignoreEnrollmentState, Context context) {
            boolean ignoreEnrollmentState, BiometricSensorPrivacy biometricSensorPrivacy) {

        if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
            return BIOMETRIC_NO_HARDWARE;
@@ -191,12 +194,10 @@ class PreAuthInfo {
                    && !ignoreEnrollmentState) {
                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)) {
            if (biometricSensorPrivacy != null && sensor.modality == TYPE_FACE) {
                if (biometricSensorPrivacy.isCameraPrivacyEnabled()) {
                    //Camera privacy is enabled as the access is disabled
                    return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                }
            }
@@ -292,13 +293,9 @@ class PreAuthInfo {
        @AuthenticatorStatus final int status;
        @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 (mBiometricSensorPrivacy != null) {
            cameraPrivacyEnabled = mBiometricSensorPrivacy.isCameraPrivacyEnabled();
        }

        if (mBiometricRequested && credentialRequested) {
@@ -315,7 +312,7 @@ class PreAuthInfo {
                    // and the face sensor privacy is enabled then return
                    // BIOMETRIC_SENSOR_PRIVACY_ENABLED.
                    //
                    // Note: This sensor will still be eligible for calls to authenticate.
                    // Note: This sensor will not be eligible for calls to authenticate.
                    status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                } else {
                    status = AUTHENTICATOR_OK;
@@ -340,7 +337,7 @@ class PreAuthInfo {
                    // 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.
                    // Note: This sensor will not be eligible for calls to authenticate.
                    status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                } else {
                    status = AUTHENTICATOR_OK;
+3 −1
Original line number Diff line number Diff line
@@ -104,6 +104,7 @@ public class AuthSessionTest {
    @Mock private KeyStore mKeyStore;
    @Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
    @Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
    @Mock BiometricSensorPrivacy mBiometricSensorPrivacy;

    private Random mRandom;
    private IBinder mToken;
@@ -571,7 +572,8 @@ public class AuthSessionTest {
                promptInfo,
                TEST_PACKAGE,
                checkDevicePolicyManager,
                mContext);
                mContext,
                mBiometricSensorPrivacy);
    }

    private AuthSession createAuthSession(List<BiometricSensor> sensors,
Loading