Require authentication-confirmation to change biometric lock settings (#5695)

* Requires authentication-confirmation to change biometric lock settings

* Prevent double authentications on older APIs when confirming settings changes

* Use new AuthPrompt API for app lock

With this commit, the app lock will only explicitly require Class 2 biometrics
or screen lock credential. Class 3 biometrics are guaranteed to meet Class 2
requirements thus will also be used when available.

* Use extension toast
This commit is contained in:
Ivan Iskandar
2021-08-20 05:10:07 +07:00
committed by GitHub
parent 26b8df5354
commit 90ab04e81d
6 changed files with 172 additions and 53 deletions

View File

@@ -4,7 +4,7 @@ import android.content.Intent
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
import eu.kanade.tachiyomi.util.view.setSecureScreen
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
@@ -28,7 +28,7 @@ class SecureActivityDelegate(private val activity: FragmentActivity) {
fun onResume() {
if (preferences.useAuthenticator().get()) {
if (AuthenticatorUtil.isSupported(activity)) {
if (activity.isAuthenticationSupported()) {
if (isAppLocked()) {
activity.startActivity(Intent(activity, UnlockActivity::class.java))
activity.overridePendingTransition(0, 0)

View File

@@ -2,51 +2,45 @@ package eu.kanade.tachiyomi.ui.security
import android.os.Bundle
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.startAuthentication
import timber.log.Timber
import java.util.Date
import java.util.concurrent.Executors
/**
* Blank activity with a BiometricPrompt.
*/
class UnlockActivity : BaseThemedActivity() {
private val executor = Executors.newSingleThreadExecutor()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val biometricPrompt = BiometricPrompt(
this,
executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
startAuthentication(
getString(R.string.unlock_app),
confirmationRequired = false,
callback = object : AuthenticatorUtil.AuthenticationCallback() {
override fun onAuthenticationError(
activity: FragmentActivity?,
errorCode: Int,
errString: CharSequence
) {
super.onAuthenticationError(activity, errorCode, errString)
Timber.e(errString.toString())
finishAffinity()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
override fun onAuthenticationSucceeded(
activity: FragmentActivity?,
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(activity, result)
SecureActivityDelegate.locked = false
preferences.lastAppUnlock().set(Date().time)
finish()
}
}
)
var promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.unlock_app))
.setAllowedAuthenticators(AuthenticatorUtil.getSupportedAuthenticators(this))
.setConfirmationRequired(false)
if (!AuthenticatorUtil.isDeviceCredentialAllowed(this)) {
promptInfo = promptInfo.setNegativeButtonText(getString(R.string.action_cancel))
}
biometricPrompt.authenticate(promptInfo.build())
}
}

View File

@@ -1,5 +1,8 @@
package eu.kanade.tachiyomi.ui.setting
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
@@ -9,6 +12,9 @@ import eu.kanade.tachiyomi.util.preference.summaryRes
import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.startAuthentication
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.launchIn
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
@@ -17,11 +23,36 @@ class SettingsSecurityController : SettingsController() {
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.pref_category_security
if (AuthenticatorUtil.isSupported(context)) {
if (context.isAuthenticationSupported()) {
switchPreference {
key = Keys.useAuthenticator
titleRes = R.string.lock_with_biometrics
defaultValue = false
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
(activity as? FragmentActivity)?.startAuthentication(
activity!!.getString(R.string.lock_with_biometrics),
activity!!.getString(R.string.confirm_lock_change),
callback = object : AuthenticatorUtil.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
activity: FragmentActivity?,
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(activity, result)
isChecked = newValue as Boolean
}
override fun onAuthenticationError(
activity: FragmentActivity?,
errorCode: Int,
errString: CharSequence
) {
super.onAuthenticationError(activity, errorCode, errString)
activity?.toast(errString.toString())
}
}
)
false
}
}
intListPreference {
key = Keys.lockAppAfter
@@ -37,6 +68,33 @@ class SettingsSecurityController : SettingsController() {
entryValues = values
defaultValue = "0"
summary = "%s"
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
if (value == newValue) return@OnPreferenceChangeListener false
(activity as? FragmentActivity)?.startAuthentication(
activity!!.getString(R.string.lock_when_idle),
activity!!.getString(R.string.confirm_lock_change),
callback = object : AuthenticatorUtil.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
activity: FragmentActivity?,
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(activity, result)
value = newValue as String
}
override fun onAuthenticationError(
activity: FragmentActivity?,
errorCode: Int,
errString: CharSequence
) {
super.onAuthenticationError(activity, errorCode, errString)
activity?.toast(errString.toString())
}
}
)
false
}
preferences.useAuthenticator().asImmediateFlow { isVisible = it }
.launchIn(viewScope)