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

Commit dea32fe8 authored by Beverly's avatar Beverly
Browse files

Move biometric errorMsg supression to its own helper class

To prepare for this method to be shared in the
future between multiple controllers

Test: atest IndicationHelperTest KeyguardIndicationControllerTest
Bug: 277102748
Change-Id: I267ab9faf09c37417f0b3982b42ca3ce444e680b
parent 59a80395
Loading
Loading
Loading
Loading
+65 −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.systemui.keyguard.util

import android.hardware.biometrics.BiometricFaceConstants
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject

@SysUISingleton
class IndicationHelper
@Inject
constructor(
    val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) {
    fun shouldSuppressErrorMsg(biometricSource: BiometricSourceType, msgId: Int): Boolean {
        return when (biometricSource) {
            BiometricSourceType.FINGERPRINT ->
                (isPrimaryAuthRequired() && !isFingerprintLockoutErrorMsg(msgId)) ||
                    msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED ||
                    msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED ||
                    msgId == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED
            BiometricSourceType.FACE ->
                (isPrimaryAuthRequired() && !isFaceLockoutErrorMsg(msgId)) ||
                    msgId == BiometricFaceConstants.FACE_ERROR_CANCELED ||
                    msgId == BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS
            else -> false
        }
    }

    private fun isFingerprintLockoutErrorMsg(msgId: Int): Boolean {
        return msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT ||
            msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
    }

    fun isFaceLockoutErrorMsg(msgId: Int): Boolean {
        return msgId == BiometricFaceConstants.FACE_ERROR_LOCKOUT ||
            msgId == BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
    }

    private fun isPrimaryAuthRequired(): Boolean {
        // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
        // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
        // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
        // check of whether non-strong biometric is allowed since strong biometrics can still be
        // used.
        return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
    }
}
+13 −28
Original line number Diff line number Diff line
@@ -56,7 +56,6 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
@@ -86,6 +85,8 @@ import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -95,8 +96,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardIndication;
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.util.IndicationHelper;
import com.android.systemui.log.LogLevel;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -227,7 +227,8 @@ public class KeyguardIndicationController {
    // triggered while the device is asleep
    private final AlarmTimeout mHideTransientMessageHandler;
    private final AlarmTimeout mHideBiometricMessageHandler;
    private FeatureFlags mFeatureFlags;
    private final FeatureFlags mFeatureFlags;
    private final IndicationHelper mIndicationHelper;

    /**
     * Creates a new KeyguardIndicationController and registers callbacks.
@@ -259,7 +260,8 @@ public class KeyguardIndicationController {
            AlarmManager alarmManager,
            UserTracker userTracker,
            BouncerMessageInteractor bouncerMessageInteractor,
            FeatureFlags flags
            FeatureFlags flags,
            IndicationHelper indicationHelper
    ) {
        mContext = context;
        mBroadcastDispatcher = broadcastDispatcher;
@@ -286,6 +288,7 @@ public class KeyguardIndicationController {
        mUserTracker = userTracker;
        mBouncerMessageInteractor = bouncerMessageInteractor;
        mFeatureFlags = flags;
        mIndicationHelper = indicationHelper;

        mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
        mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
@@ -1249,13 +1252,13 @@ public class KeyguardIndicationController {
        private void onFaceAuthError(int msgId, String errString) {
            CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
            mFaceAcquiredMessageDeferral.reset();
            if (shouldSuppressFaceError(msgId)) {
                mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
            if (mIndicationHelper.shouldSuppressErrorMsg(FACE, msgId)) {
                mKeyguardLogger.logBiometricMessage("KIC suppressingFaceError", msgId, errString);
                return;
            }
            if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
                handleFaceAuthTimeoutError(deferredFaceMessage);
            } else if (isLockoutError(msgId)) {
            } else if (mIndicationHelper.isFaceLockoutErrorMsg(msgId)) {
                handleFaceLockoutError(errString);
            } else {
                showErrorMessageNowOrLater(errString, null);
@@ -1263,8 +1266,8 @@ public class KeyguardIndicationController {
        }

        private void onFingerprintAuthError(int msgId, String errString) {
            if (shouldSuppressFingerprintError(msgId)) {
                mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
            if (mIndicationHelper.shouldSuppressErrorMsg(FINGERPRINT, msgId)) {
                mKeyguardLogger.logBiometricMessage("KIC suppressingFingerprintError",
                        msgId,
                        errString);
            } else {
@@ -1272,19 +1275,6 @@ public class KeyguardIndicationController {
            }
        }

        private boolean shouldSuppressFingerprintError(int msgId) {
            return ((isPrimaryAuthRequired() && !isLockoutError(msgId))
                    || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
                    || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
                    || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
        }

        private boolean shouldSuppressFaceError(int msgId) {
            return ((isPrimaryAuthRequired() && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
                    || msgId == FaceManager.FACE_ERROR_CANCELED
                    || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS);
        }

        @Override
        public void onTrustChanged(int userId) {
            if (!isCurrentUser(userId)) return;
@@ -1408,11 +1398,6 @@ public class KeyguardIndicationController {
        return mContext.getString(followupMsgId);
    }

    private static boolean isLockoutError(int msgId) {
        return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
                || msgId == FaceManager.FACE_ERROR_LOCKOUT;
    }

    private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
        mKeyguardLogger.logBiometricMessage("deferred message after face auth timeout",
                null, String.valueOf(deferredFaceMessage));
+173 −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.systemui.keyguard.util

import android.hardware.biometrics.BiometricFaceConstants.BIOMETRIC_ERROR_POWER_PRESSED
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_CANCELED
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_TIMEOUT
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED
import android.hardware.biometrics.BiometricSourceType
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit

@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class IndicationHelperTest : SysuiTestCase() {

    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()

    @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
    private lateinit var underTest: IndicationHelper

    @Before
    fun setup() {
        underTest =
            IndicationHelper(
                keyguardUpdateMonitor,
            )
    }

    @Test
    fun suppressErrorMsg_faceErrorCancelled() {
        givenPrimaryAuthNotRequired()
        assertTrue(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_CANCELED))
    }

    @Test
    fun suppressErrorMsg_faceErrorUnableToProcess() {
        givenPrimaryAuthNotRequired()
        assertTrue(
            underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_UNABLE_TO_PROCESS)
        )
    }

    @Test
    fun suppressErrorMsg_facePrimaryAuthRequired() {
        givenPrimaryAuthRequired()
        assertTrue(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_TIMEOUT))
    }

    @Test
    fun doNotSuppressErrorMsg_facePrimaryAuthRequired_faceLockout() {
        givenPrimaryAuthRequired()
        assertFalse(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_LOCKOUT))
        assertFalse(
            underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_LOCKOUT_PERMANENT)
        )
    }

    @Test
    fun suppressErrorMsg_fingerprintErrorCancelled() {
        givenPrimaryAuthNotRequired()
        assertTrue(
            underTest.shouldSuppressErrorMsg(
                BiometricSourceType.FINGERPRINT,
                FINGERPRINT_ERROR_CANCELED
            )
        )
    }

    @Test
    fun suppressErrorMsg_fingerprintErrorUserCancelled() {
        givenPrimaryAuthNotRequired()
        assertTrue(
            underTest.shouldSuppressErrorMsg(
                BiometricSourceType.FINGERPRINT,
                FINGERPRINT_ERROR_USER_CANCELED
            )
        )
    }

    @Test
    fun suppressErrorMsg_fingerprintErrorPowerPressed() {
        givenPrimaryAuthNotRequired()
        assertTrue(
            underTest.shouldSuppressErrorMsg(
                BiometricSourceType.FINGERPRINT,
                BIOMETRIC_ERROR_POWER_PRESSED
            )
        )
    }

    @Test
    fun suppressErrorMsg_fingerprintPrimaryAuthRequired() {
        givenPrimaryAuthRequired()
        assertTrue(
            underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FINGERPRINT_ERROR_TIMEOUT)
        )
    }

    @Test
    fun doNotSuppressErrorMsg_fingerprintPrimaryAuthRequired_fingerprintLockout() {
        givenPrimaryAuthRequired()
        assertFalse(
            underTest.shouldSuppressErrorMsg(
                BiometricSourceType.FINGERPRINT,
                FINGERPRINT_ERROR_LOCKOUT
            )
        )
        assertFalse(
            underTest.shouldSuppressErrorMsg(
                BiometricSourceType.FACE,
                FINGERPRINT_ERROR_LOCKOUT_PERMANENT
            )
        )
    }

    @Test
    fun isFaceLockoutErrorMsgId() {
        givenPrimaryAuthRequired()
        assertTrue(underTest.isFaceLockoutErrorMsg(FACE_ERROR_LOCKOUT))
        assertTrue(underTest.isFaceLockoutErrorMsg(FACE_ERROR_LOCKOUT_PERMANENT))
        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_TIMEOUT))
        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_CANCELED))
        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_UNABLE_TO_PROCESS))
        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_VENDOR))
    }

    private fun givenPrimaryAuthNotRequired() {
        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
            .thenReturn(true)
    }

    private fun givenPrimaryAuthRequired() {
        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
            .thenReturn(false)
    }
}
+14 −23
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ import android.content.Intent;
import android.content.pm.UserInfo;
import android.graphics.Color;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager;
@@ -98,14 +99,15 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardIndication;
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.util.IndicationHelper;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
@@ -213,6 +215,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
    private StatusBarStateController.StateListener mStatusBarStateListener;
    private ScreenLifecycle.Observer mScreenObserver;
    private BroadcastReceiver mBroadcastReceiver;
    private IndicationHelper mIndicationHelper;
    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
    private TestableLooper mTestableLooper;
    private final int mCurrentUserId = 1;
@@ -262,13 +265,14 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
        when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);


        when(mDevicePolicyResourcesManager.getString(anyString(), any()))
                .thenReturn(mDisclosureGeneric);
        when(mDevicePolicyResourcesManager.getString(anyString(), any(), anyString()))
                .thenReturn(mDisclosureWithOrganization);
        when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);

        mIndicationHelper = new IndicationHelper(mKeyguardUpdateMonitor);

        mWakeLock = new WakeLockFake();
        mWakeLockBuilder = new WakeLockFake.Builder(mContext);
        mWakeLockBuilder.setWakeLock(mWakeLock);
@@ -304,7 +308,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
                mAlarmManager,
                mUserTracker,
                mock(BouncerMessageInteractor.class),
                flags
                flags,
                mIndicationHelper
        );
        mController.init();
        mController.setIndicationArea(mIndicationArea);
@@ -805,33 +810,19 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
    }

    @Test
    public void transientIndication_visibleWhenDozing_ignoresFingerprintCancellation() {
    public void transientIndication_visibleWhenDozing_ignoresFingerprintErrorMsg() {
        createController();

        mController.setVisible(true);
        reset(mRotateTextViewController);
        mController.getKeyguardCallback().onBiometricError(
                FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED, "foo",
                BiometricSourceType.FINGERPRINT);
        mController.getKeyguardCallback().onBiometricError(
                FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar",
                BiometricSourceType.FINGERPRINT);

        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
        verifyNoMessage(INDICATION_TYPE_TRANSIENT);
    }

    @Test
    public void transientIndication_visibleWhenDozing_ignoresPowerPressed() {
        createController();

        mController.setVisible(true);
        reset(mRotateTextViewController);
        // WHEN a fingerprint error user cancelled message is received
        mController.getKeyguardCallback().onBiometricError(
                FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "foo",
                BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED, "foo",
                BiometricSourceType.FINGERPRINT);

        // THEN no message is shown
        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
        verifyNoMessage(INDICATION_TYPE_TRANSIENT);
    }

    @Test