blob: 38fa354534188c741c7493e9fd43270d4807683c [file] [log] [blame]
/*
* 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.annotation.IntDef
import android.content.ContentResolver
import android.database.ContentObserver
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE
import android.util.Log
import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.settings.SecureSettings
import java.io.PrintWriter
import javax.inject.Inject
/**
* Handles active unlock settings changes.
*/
@SysUISingleton
class ActiveUnlockConfig @Inject constructor(
@Main private val handler: Handler,
private val secureSettings: SecureSettings,
private val contentResolver: ContentResolver,
dumpManager: DumpManager
) : Dumpable {
companion object {
const val TAG = "ActiveUnlockConfig"
const val BIOMETRIC_TYPE_NONE = 0
const val BIOMETRIC_TYPE_ANY_FACE = 1
const val BIOMETRIC_TYPE_ANY_FINGERPRINT = 2
const val BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT = 3
}
@Retention(AnnotationRetention.SOURCE)
@IntDef(BIOMETRIC_TYPE_NONE, BIOMETRIC_TYPE_ANY_FACE, BIOMETRIC_TYPE_ANY_FINGERPRINT,
BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT)
annotation class BiometricType
/**
* Indicates the origin for an active unlock request.
*/
enum class ACTIVE_UNLOCK_REQUEST_ORIGIN {
WAKE, UNLOCK_INTENT, BIOMETRIC_FAIL, ASSISTANT
}
var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null
private var requestActiveUnlockOnWakeup = false
private var requestActiveUnlockOnUnlockIntent = false
private var requestActiveUnlockOnBioFail = false
private var faceErrorsToTriggerBiometricFailOn = mutableSetOf(FACE_ERROR_TIMEOUT)
private var faceAcquireInfoToTriggerBiometricFailOn = mutableSetOf<Int>()
private var onUnlockIntentWhenBiometricEnrolled = mutableSetOf<Int>(BIOMETRIC_TYPE_NONE)
private val settingsObserver = object : ContentObserver(handler) {
private val wakeUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)
private val unlockIntentUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)
private val bioFailUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)
private val faceErrorsUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS)
private val faceAcquireInfoUri =
secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)
private val unlockIntentWhenBiometricEnrolledUri =
secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
fun register() {
registerUri(
listOf(
wakeUri,
unlockIntentUri,
bioFailUri,
faceErrorsUri,
faceAcquireInfoUri,
unlockIntentWhenBiometricEnrolledUri
)
)
onChange(true, ArrayList(), 0, getCurrentUser())
}
private fun registerUri(uris: Collection<Uri>) {
for (uri in uris) {
contentResolver.registerContentObserver(
uri,
false,
this,
UserHandle.USER_ALL)
}
}
override fun onChange(
selfChange: Boolean,
uris: Collection<Uri>,
flags: Int,
userId: Int
) {
if (getCurrentUser() != userId) {
return
}
if (selfChange || uris.contains(wakeUri)) {
requestActiveUnlockOnWakeup = secureSettings.getIntForUser(
ACTIVE_UNLOCK_ON_WAKE, 0, getCurrentUser()) == 1
}
if (selfChange || uris.contains(unlockIntentUri)) {
requestActiveUnlockOnUnlockIntent = secureSettings.getIntForUser(
ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0, getCurrentUser()) == 1
}
if (selfChange || uris.contains(bioFailUri)) {
requestActiveUnlockOnBioFail = secureSettings.getIntForUser(
ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1
}
if (selfChange || uris.contains(faceErrorsUri)) {
processStringArray(
secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS,
getCurrentUser()),
faceErrorsToTriggerBiometricFailOn,
setOf(FACE_ERROR_TIMEOUT))
}
if (selfChange || uris.contains(faceAcquireInfoUri)) {
processStringArray(
secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
getCurrentUser()),
faceAcquireInfoToTriggerBiometricFailOn,
setOf<Int>())
}
if (selfChange || uris.contains(unlockIntentWhenBiometricEnrolledUri)) {
processStringArray(
secureSettings.getStringForUser(
ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
getCurrentUser()),
onUnlockIntentWhenBiometricEnrolled,
setOf(BIOMETRIC_TYPE_NONE))
}
}
/**
* Convert a pipe-separated set of integers into a set of ints.
* @param stringSetting expected input are integers delineated by a pipe. For example,
* it may look something like this: "1|5|3".
* @param out updates the "out" Set will the integers between the pipes.
* @param default If stringSetting is null, "out" will be populated with values in "default"
*/
private fun processStringArray(
stringSetting: String?,
out: MutableSet<Int>,
default: Set<Int>
) {
out.clear()
stringSetting?.let {
for (code: String in stringSetting.split("|")) {
try {
out.add(code.toInt())
} catch (e: NumberFormatException) {
Log.e(TAG, "Passed an invalid setting=$code")
}
}
} ?: out.addAll(default)
}
}
init {
settingsObserver.register()
dumpManager.registerDumpable(this)
}
/**
* If any active unlock triggers are enabled.
*/
fun isActiveUnlockEnabled(): Boolean {
return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent ||
requestActiveUnlockOnBioFail
}
/**
* Whether the face error code from {@link BiometricFaceConstants} should trigger
* active unlock on biometric failure.
*/
fun shouldRequestActiveUnlockOnFaceError(errorCode: Int): Boolean {
return faceErrorsToTriggerBiometricFailOn.contains(errorCode)
}
/**
* Whether the face acquireInfo from {@link BiometricFaceConstants} should trigger
* active unlock on biometric failure.
*/
fun shouldRequestActiveUnlockOnFaceAcquireInfo(acquiredInfo: Int): Boolean {
return faceAcquireInfoToTriggerBiometricFailOn.contains(acquiredInfo)
}
/**
* Whether to trigger active unlock based on where the request is coming from and
* the current settings.
*/
fun shouldAllowActiveUnlockFromOrigin(requestOrigin: ACTIVE_UNLOCK_REQUEST_ORIGIN): Boolean {
return when (requestOrigin) {
ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE -> requestActiveUnlockOnWakeup
ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT ->
requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup ||
(shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment())
ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL ->
requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntent ||
requestActiveUnlockOnWakeup
ACTIVE_UNLOCK_REQUEST_ORIGIN.ASSISTANT -> isActiveUnlockEnabled()
}
}
private fun shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment(): Boolean {
if (!requestActiveUnlockOnBioFail) {
return false
}
keyguardUpdateMonitor?.let {
val anyFaceEnrolled = it.isFaceEnrolled
val anyFingerprintEnrolled =
it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())
val udfpsEnrolled = it.isUdfpsEnrolled
if (!anyFaceEnrolled && !anyFingerprintEnrolled) {
return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_NONE)
}
if (!anyFaceEnrolled && anyFingerprintEnrolled) {
return onUnlockIntentWhenBiometricEnrolled.contains(
BIOMETRIC_TYPE_ANY_FINGERPRINT) ||
(udfpsEnrolled && onUnlockIntentWhenBiometricEnrolled.contains(
BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT))
}
if (!anyFingerprintEnrolled && anyFaceEnrolled) {
return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_ANY_FACE)
}
}
return false
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("Settings:")
pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup")
pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent")
pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail")
pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" +
"$onUnlockIntentWhenBiometricEnrolled")
pw.println(" requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn")
pw.println(" requestActiveUnlockOnFaceAcquireInfo=" +
"$faceAcquireInfoToTriggerBiometricFailOn")
pw.println("Current state:")
keyguardUpdateMonitor?.let {
pw.println(" shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" +
"${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}")
pw.println(" faceEnrolled=${it.isFaceEnrolled}")
pw.println(" fpEnrolled=${
it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())}")
pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}")
} ?: pw.println(" keyguardUpdateMonitor is uninitialized")
}
}