mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-16 22:17:28 +01:00
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:
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user