Merge "Log biometric lockout events"

This commit is contained in:
Beverly Tai
2022-01-12 13:39:37 +00:00
committed by Android (Google) Code Review
6 changed files with 389 additions and 5 deletions

View File

@@ -294,6 +294,7 @@
<string-array name="config_systemUIServiceComponents" translatable="false">
<item>com.android.systemui.util.NotificationChannels</item>
<item>com.android.systemui.keyguard.KeyguardViewMediator</item>
<item>com.android.keyguard.KeyguardBiometricLockoutLogger</item>
<item>com.android.systemui.recents.Recents</item>
<item>com.android.systemui.volume.VolumeUI</item>
<item>com.android.systemui.statusbar.phone.StatusBar</item>

View File

@@ -0,0 +1,176 @@
/*
* Copyright (C) 2022 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.keyguard
import android.content.Context
import android.hardware.biometrics.BiometricSourceType
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
import com.android.keyguard.KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject
/**
* Logs events when primary authentication requirements change. Primary authentication is considered
* authentication using pin/pattern/password input.
*
* See [PrimaryAuthRequiredEvent] for all the events and their descriptions.
*/
@SysUISingleton
class KeyguardBiometricLockoutLogger @Inject constructor(
context: Context?,
private val uiEventLogger: UiEventLogger,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dumpManager: DumpManager
) : CoreStartable(context) {
private var fingerprintLockedOut = false
private var faceLockedOut = false
private var encryptedOrLockdown = false
private var unattendedUpdate = false
private var timeout = false
override fun start() {
dumpManager.registerDumpable(this)
mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged(
KeyguardUpdateMonitor.getCurrentUser())
keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback)
}
private val mKeyguardUpdateMonitorCallback: KeyguardUpdateMonitorCallback =
object : KeyguardUpdateMonitorCallback() {
override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
val lockedOut = keyguardUpdateMonitor.isFingerprintLockedOut
if (lockedOut && !fingerprintLockedOut) {
uiEventLogger.log(
PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
} else if (!lockedOut && fingerprintLockedOut) {
uiEventLogger.log(
PrimaryAuthRequiredEvent
.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
}
fingerprintLockedOut = lockedOut
} else if (biometricSourceType == BiometricSourceType.FACE) {
val lockedOut = keyguardUpdateMonitor.isFaceLockedOut
if (lockedOut && !faceLockedOut) {
uiEventLogger.log(
PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
} else if (!lockedOut && faceLockedOut) {
uiEventLogger.log(
PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
}
faceLockedOut = lockedOut
}
}
override fun onStrongAuthStateChanged(userId: Int) {
if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
return
}
val strongAuthFlags = keyguardUpdateMonitor.strongAuthTracker
.getStrongAuthForUser(userId)
val newEncryptedOrLockdown = keyguardUpdateMonitor.isEncryptedOrLockdown(userId)
if (newEncryptedOrLockdown && !encryptedOrLockdown) {
uiEventLogger.log(
PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
}
encryptedOrLockdown = newEncryptedOrLockdown
val newUnattendedUpdate = isUnattendedUpdate(strongAuthFlags)
if (newUnattendedUpdate && !unattendedUpdate) {
uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
}
unattendedUpdate = newUnattendedUpdate
val newTimeout = isStrongAuthTimeout(strongAuthFlags)
if (newTimeout && !timeout) {
uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT)
}
timeout = newTimeout
}
}
private fun isUnattendedUpdate(
@LockPatternUtils.StrongAuthTracker.StrongAuthFlags flags: Int
) = containsFlag(flags, STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE)
private fun isStrongAuthTimeout(
@LockPatternUtils.StrongAuthTracker.StrongAuthFlags flags: Int
) = containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) ||
containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT)
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
pw.println(" mFingerprintLockedOut=$fingerprintLockedOut")
pw.println(" mFaceLockedOut=$faceLockedOut")
pw.println(" mIsEncryptedOrLockdown=$encryptedOrLockdown")
pw.println(" mIsUnattendedUpdate=$unattendedUpdate")
pw.println(" mIsTimeout=$timeout")
}
/**
* Events pertaining to whether primary authentication (pin/pattern/password input) is required
* for device entry.
*/
@VisibleForTesting
enum class PrimaryAuthRequiredEvent(private val mId: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "Fingerprint cannot be used to authenticate for device entry. This" +
"can persist until the next primary auth or may timeout.")
PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT(924),
@UiEvent(doc = "Fingerprint can be used to authenticate for device entry.")
PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET(925),
@UiEvent(doc = "Face cannot be used to authenticate for device entry.")
PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT(926),
@UiEvent(doc = "Face can be used to authenticate for device entry.")
PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET(927),
@UiEvent(doc = "Device is encrypted (ie: after reboot) or device is locked down by DPM " +
"or a manual user lockdown.")
PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN(928),
@UiEvent(doc = "Primary authentication is required because it hasn't been used for a " +
"time required by a device admin or because primary auth hasn't been used for a " +
"time after a non-strong biometric (weak or convenience) is used to unlock the " +
"device.")
PRIMARY_AUTH_REQUIRED_TIMEOUT(929),
@UiEvent(doc = "Strong authentication is required to prepare for unattended upgrade.")
PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE(931);
override fun getId(): Int {
return mId
}
}
companion object {
private fun containsFlag(strongAuthFlags: Int, flagCheck: Int): Boolean {
return strongAuthFlags and flagCheck != 0
}
}
}

View File

@@ -183,7 +183,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int MSG_USER_STOPPED = 340;
private static final int MSG_USER_REMOVED = 341;
private static final int MSG_KEYGUARD_GOING_AWAY = 342;
private static final int MSG_LOCK_SCREEN_MODE = 343;
private static final int MSG_TIME_FORMAT_UPDATE = 344;
private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
@@ -221,7 +220,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
/**
* If no cancel signal has been received after this amount of time, set the biometric running
* state to stopped to allow Keyguard to retry authentication.
@@ -231,7 +229,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName(
"com.android.settings", "com.android.settings.FallbackHome");
/**
* If true, the system is in the half-boot-to-decryption-screen state.
* Prudently disable lockscreen.
@@ -1250,7 +1247,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
}
private boolean isEncryptedOrLockdown(int userId) {
/**
* Returns true if primary authentication is required for the given user due to lockdown
* or encryption after reboot.
*/
public boolean isEncryptedOrLockdown(int userId) {
final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(userId);
final boolean isLockDown =
containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
@@ -2514,6 +2515,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return mFingerprintLockedOut || mFingerprintLockedOutPermanent;
}
public boolean isFaceLockedOut() {
return mFaceLockedOutPermanent;
}
/**
* If biometrics hardware is available, not disabled, and user has enrolled templates.
* This does NOT check if the device is encrypted or in lockdown.

View File

@@ -16,6 +16,7 @@
package com.android.systemui.dagger;
import com.android.keyguard.KeyguardBiometricLockoutLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.LatencyTester;
import com.android.systemui.ScreenDecorations;
@@ -90,6 +91,13 @@ public abstract class SystemUIBinder {
@ClassKey(KeyguardViewMediator.class)
public abstract CoreStartable bindKeyguardViewMediator(KeyguardViewMediator sysui);
/** Inject into KeyguardBiometricLockoutLogger. */
@Binds
@IntoMap
@ClassKey(KeyguardBiometricLockoutLogger.class)
public abstract CoreStartable bindKeyguardBiometricLockoutLogger(
KeyguardBiometricLockoutLogger sysui);
/** Inject into LatencyTests. */
@Binds
@IntoMap

View File

@@ -11,7 +11,7 @@
* 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
* limitations under the License.
*/
package com.android.systemui.statusbar.phone;

View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2022 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.keyguard
import android.hardware.biometrics.BiometricSourceType
import org.mockito.Mockito.verify
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() {
@Mock
lateinit var uiEventLogger: UiEventLogger
@Mock
lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock
lateinit var dumpManager: DumpManager
@Mock
lateinit var strongAuthTracker: KeyguardUpdateMonitor.StrongAuthTracker
@Captor
lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
lateinit var updateMonitorCallback: KeyguardUpdateMonitorCallback
lateinit var keyguardBiometricLockoutLogger: KeyguardBiometricLockoutLogger
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker)
keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
mContext,
uiEventLogger,
keyguardUpdateMonitor,
dumpManager)
}
@Test
fun test_logsOnStart() {
// GIVEN is encrypted / lockdown before start
whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(anyInt()))
.thenReturn(true)
// WHEN start
keyguardBiometricLockoutLogger.start()
// THEN encrypted / lockdown state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
}
@Test
fun test_logTimeoutChange() {
keyguardBiometricLockoutLogger.start()
captureUpdateMonitorCallback()
// GIVEN primary auth required b/c timeout
whenever(strongAuthTracker.getStrongAuthForUser(anyInt()))
.thenReturn(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
// WHEN primary auth requirement changes
updateMonitorCallback.onStrongAuthStateChanged(0)
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
.PRIMARY_AUTH_REQUIRED_TIMEOUT)
}
@Test
fun test_logUnattendedUpdate() {
keyguardBiometricLockoutLogger.start()
captureUpdateMonitorCallback()
// GIVEN primary auth required b/c unattended update
whenever(strongAuthTracker.getStrongAuthForUser(anyInt()))
.thenReturn(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE)
// WHEN primary auth requirement changes
updateMonitorCallback.onStrongAuthStateChanged(0)
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
}
@Test
fun test_logMultipleChanges() {
keyguardBiometricLockoutLogger.start()
captureUpdateMonitorCallback()
// GIVEN primary auth required b/c timeout
whenever(strongAuthTracker.getStrongAuthForUser(anyInt()))
.thenReturn(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
or STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE)
// WHEN primary auth requirement changes
updateMonitorCallback.onStrongAuthStateChanged(0)
// THEN primary auth required state is logged with all the reasons
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
.PRIMARY_AUTH_REQUIRED_TIMEOUT)
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
// WHEN onStrongAuthStateChanged is called again
updateMonitorCallback.onStrongAuthStateChanged(0)
// THEN no more events are sent since there haven't been any changes
verifyNoMoreInteractions(uiEventLogger)
}
@Test
fun test_logFaceLockout() {
keyguardBiometricLockoutLogger.start()
captureUpdateMonitorCallback()
// GIVEN primary auth required b/c face lock
whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(true)
// WHEN lockout state changes
updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE)
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
// WHEN face lockout is reset
whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(false)
updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE)
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
}
@Test
fun test_logFingerprintLockout() {
keyguardBiometricLockoutLogger.start()
captureUpdateMonitorCallback()
// GIVEN primary auth required b/c fingerprint lock
whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
// WHEN lockout state changes
updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
// WHEN fingerprint lockout is reset
whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
}
fun captureUpdateMonitorCallback() {
verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallbackCaptor.capture())
updateMonitorCallback = updateMonitorCallbackCaptor.value
}
}