mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge pull request #1968 from vector-im/feature/biometrics
Improve PIN code feature
This commit is contained in:
commit
187edbd32a
@ -42,7 +42,7 @@ allprojects {
|
||||
// PhotoView
|
||||
includeGroupByRegex 'com\\.github\\.chrisbanes'
|
||||
// PFLockScreen-Android
|
||||
includeGroupByRegex 'com\\.github\\.ganfra'
|
||||
includeGroupByRegex 'com\\.github\\.vector-im'
|
||||
}
|
||||
}
|
||||
maven {
|
||||
|
@ -346,7 +346,7 @@ dependencies {
|
||||
implementation 'me.saket:better-link-movement-method:2.2.0'
|
||||
implementation 'com.google.android:flexbox:1.1.1'
|
||||
implementation "androidx.autofill:autofill:$autofill_version"
|
||||
implementation 'com.github.ganfra:PFLockScreen-Android:1.0.0-beta8'
|
||||
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta9'
|
||||
|
||||
// Custom Tab
|
||||
implementation 'androidx.browser:browser:1.2.0'
|
||||
|
@ -396,6 +396,11 @@ SOFTWARE.
|
||||
<br/>
|
||||
Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
</li>
|
||||
<li>
|
||||
<b>PFLockScreen-Android</b>
|
||||
<br/>
|
||||
Copyright 2018, Aleksandr Nikiforov
|
||||
</li>
|
||||
</ul>
|
||||
<pre>
|
||||
Apache License
|
||||
|
@ -37,6 +37,25 @@ interface PinCodeStore {
|
||||
fun getEncodedPin(): String?
|
||||
|
||||
suspend fun hasEncodedPin(): Boolean
|
||||
|
||||
fun getRemainingPinCodeAttemptsNumber(): Int
|
||||
|
||||
fun getRemainingBiometricsAttemptsNumber(): Int
|
||||
|
||||
/**
|
||||
* Will return the number of remaining attempts
|
||||
*/
|
||||
fun onWrongPin(): Int
|
||||
|
||||
/**
|
||||
* Will return the number of remaining attempts
|
||||
*/
|
||||
fun onWrongBiometrics(): Int
|
||||
|
||||
/**
|
||||
* Will reset the counters
|
||||
*/
|
||||
fun resetCounters()
|
||||
}
|
||||
|
||||
class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: SharedPreferences) : PinCodeStore {
|
||||
@ -48,6 +67,8 @@ class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences:
|
||||
}
|
||||
|
||||
override suspend fun deleteEncodedPin() = withContext(Dispatchers.IO) {
|
||||
// Also reset the counters
|
||||
resetCounters()
|
||||
sharedPreferences.edit {
|
||||
remove(ENCODED_PIN_CODE_KEY)
|
||||
}
|
||||
@ -72,11 +93,47 @@ class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences:
|
||||
result.error == null && result.result
|
||||
}
|
||||
|
||||
override fun getRemainingPinCodeAttemptsNumber(): Int {
|
||||
return sharedPreferences.getInt(REMAINING_PIN_CODE_ATTEMPTS_KEY, MAX_PIN_CODE_ATTEMPTS_NUMBER_BEFORE_LOGOUT)
|
||||
}
|
||||
|
||||
override fun getRemainingBiometricsAttemptsNumber(): Int {
|
||||
return sharedPreferences.getInt(REMAINING_BIOMETRICS_ATTEMPTS_KEY, MAX_BIOMETRIC_ATTEMPTS_NUMBER_BEFORE_FORCE_PIN)
|
||||
}
|
||||
|
||||
override fun onWrongPin(): Int {
|
||||
val remaining = getRemainingPinCodeAttemptsNumber() - 1
|
||||
sharedPreferences.edit {
|
||||
putInt(REMAINING_PIN_CODE_ATTEMPTS_KEY, remaining)
|
||||
}
|
||||
return remaining
|
||||
}
|
||||
|
||||
override fun onWrongBiometrics(): Int {
|
||||
val remaining = getRemainingBiometricsAttemptsNumber() - 1
|
||||
sharedPreferences.edit {
|
||||
putInt(REMAINING_BIOMETRICS_ATTEMPTS_KEY, remaining)
|
||||
}
|
||||
return remaining
|
||||
}
|
||||
|
||||
override fun resetCounters() {
|
||||
sharedPreferences.edit {
|
||||
remove(REMAINING_PIN_CODE_ATTEMPTS_KEY)
|
||||
remove(REMAINING_BIOMETRICS_ATTEMPTS_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun <T> awaitPinCodeCallback(crossinline callback: (PFPinCodeHelperCallback<T>) -> Unit) = suspendCoroutine<PFResult<T>> { cont ->
|
||||
callback(PFPinCodeHelperCallback<T> { result -> cont.resume(result) })
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ENCODED_PIN_CODE_KEY = "ENCODED_PIN_CODE_KEY"
|
||||
private const val REMAINING_PIN_CODE_ATTEMPTS_KEY = "REMAINING_PIN_CODE_ATTEMPTS_KEY"
|
||||
private const val REMAINING_BIOMETRICS_ATTEMPTS_KEY = "REMAINING_BIOMETRICS_ATTEMPTS_KEY"
|
||||
|
||||
private const val MAX_PIN_CODE_ATTEMPTS_NUMBER_BEFORE_LOGOUT = 3
|
||||
private const val MAX_BIOMETRIC_ATTEMPTS_NUMBER_BEFORE_FORCE_PIN = 5
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import com.beautycoder.pflockscreen.fragments.PFLockScreenFragment
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.replaceFragment
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.features.MainActivity
|
||||
import im.vector.app.features.MainActivityArgs
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
@ -61,7 +62,7 @@ class PinFragment @Inject constructor(
|
||||
val encodedPin = pinCodeStore.getEncodedPin() ?: return
|
||||
val authFragment = PFLockScreenFragment()
|
||||
val builder = PFFLockScreenConfiguration.Builder(requireContext())
|
||||
.setUseFingerprint(true)
|
||||
.setUseBiometric(pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0)
|
||||
.setTitle(getString(R.string.auth_pin_confirm_to_disable_title))
|
||||
.setClearCodeOnError(true)
|
||||
.setMode(PFFLockScreenConfiguration.MODE_AUTH)
|
||||
@ -69,9 +70,10 @@ class PinFragment @Inject constructor(
|
||||
authFragment.setEncodedPinCode(encodedPin)
|
||||
authFragment.setLoginListener(object : PFLockScreenFragment.OnPFLockScreenLoginListener {
|
||||
override fun onPinLoginFailed() {
|
||||
onWrongPin()
|
||||
}
|
||||
|
||||
override fun onFingerprintSuccessful() {
|
||||
override fun onBiometricAuthSuccessful() {
|
||||
lifecycleScope.launch {
|
||||
pinCodeStore.deleteEncodedPin()
|
||||
vectorBaseActivity.setResult(Activity.RESULT_OK)
|
||||
@ -79,7 +81,13 @@ class PinFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFingerprintLoginFailed() {
|
||||
override fun onBiometricAuthLoginFailed() {
|
||||
val remainingAttempts = pinCodeStore.onWrongBiometrics()
|
||||
if (remainingAttempts <= 0) {
|
||||
// Disable Biometrics
|
||||
builder.setUseBiometric(false)
|
||||
authFragment.setConfiguration(builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCodeInputSuccessful() {
|
||||
@ -121,8 +129,12 @@ class PinFragment @Inject constructor(
|
||||
private fun showAuthFragment() {
|
||||
val encodedPin = pinCodeStore.getEncodedPin() ?: return
|
||||
val authFragment = PFLockScreenFragment()
|
||||
val canUseBiometrics = pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0
|
||||
val builder = PFFLockScreenConfiguration.Builder(requireContext())
|
||||
.setUseFingerprint(true)
|
||||
.setUseBiometric(true)
|
||||
.setAutoShowBiometric(true)
|
||||
.setUseBiometric(canUseBiometrics)
|
||||
.setAutoShowBiometric(canUseBiometrics)
|
||||
.setTitle(getString(R.string.auth_pin_title))
|
||||
.setLeftButton(getString(R.string.auth_pin_forgot))
|
||||
.setClearCodeOnError(true)
|
||||
@ -134,17 +146,26 @@ class PinFragment @Inject constructor(
|
||||
}
|
||||
authFragment.setLoginListener(object : PFLockScreenFragment.OnPFLockScreenLoginListener {
|
||||
override fun onPinLoginFailed() {
|
||||
onWrongPin()
|
||||
}
|
||||
|
||||
override fun onFingerprintSuccessful() {
|
||||
override fun onBiometricAuthSuccessful() {
|
||||
pinCodeStore.resetCounters()
|
||||
vectorBaseActivity.setResult(Activity.RESULT_OK)
|
||||
vectorBaseActivity.finish()
|
||||
}
|
||||
|
||||
override fun onFingerprintLoginFailed() {
|
||||
override fun onBiometricAuthLoginFailed() {
|
||||
val remainingAttempts = pinCodeStore.onWrongBiometrics()
|
||||
if (remainingAttempts <= 0) {
|
||||
// Disable Biometrics
|
||||
builder.setUseBiometric(false)
|
||||
authFragment.setConfiguration(builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCodeInputSuccessful() {
|
||||
pinCodeStore.resetCounters()
|
||||
vectorBaseActivity.setResult(Activity.RESULT_OK)
|
||||
vectorBaseActivity.finish()
|
||||
}
|
||||
@ -152,6 +173,21 @@ class PinFragment @Inject constructor(
|
||||
replaceFragment(R.id.pinFragmentContainer, authFragment)
|
||||
}
|
||||
|
||||
private fun onWrongPin() {
|
||||
val remainingAttempts = pinCodeStore.onWrongPin()
|
||||
when {
|
||||
remainingAttempts > 1 ->
|
||||
requireActivity().toast(resources.getQuantityString(R.plurals.wrong_pin_message_remaining_attempts, remainingAttempts, remainingAttempts))
|
||||
remainingAttempts == 1 ->
|
||||
requireActivity().toast(R.string.wrong_pin_message_last_remaining_attempt)
|
||||
else -> {
|
||||
requireActivity().toast(R.string.too_many_pin_failures)
|
||||
// Logout
|
||||
MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCredentials = true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayForgotPinWarningDialog() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(getString(R.string.auth_pin_reset_title))
|
||||
|
@ -2517,6 +2517,12 @@
|
||||
|
||||
<string name="alert_push_are_disabled_title">Push notifications are disabled</string>
|
||||
<string name="alert_push_are_disabled_description">Review your settings to enable push notifications</string>
|
||||
<plurals name="wrong_pin_message_remaining_attempts">
|
||||
<item quantity="one">Wrong code, %d remaining attempt</item>
|
||||
<item quantity="other">Wrong code, %d remaining attempts</item>
|
||||
</plurals>
|
||||
<string name="wrong_pin_message_last_remaining_attempt">Warning! Last remaining attempt before logout!</string>
|
||||
<string name="too_many_pin_failures">Too many errors, you\'ve been logged out</string>
|
||||
<string name="create_pin_title">Choose a PIN for security</string>
|
||||
<string name="create_pin_confirm_title">Confirm PIN</string>
|
||||
<string name="create_pin_confirm_failure">Failed to validate pin, please tap a new one.</string>
|
||||
|
Loading…
Reference in New Issue
Block a user