Merge pull request #573 from PhilKes/fix/biometric-lock-open-note

Fix/biometric lock open note
This commit is contained in:
Phil 2025-05-06 18:41:46 +02:00 committed by GitHub
commit fb35ffdac4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 63 additions and 174 deletions

View file

@ -190,7 +190,7 @@ dependencies {
implementation("androidx.security:security-crypto:1.1.0-alpha06")
implementation("androidx.sqlite:sqlite-ktx:2.4.0")
implementation("androidx.work:work-runtime:2.9.1")
implementation("androidx.biometric:biometric:1.1.0")
implementation("cat.ereza:customactivityoncrash:2.4.0")
implementation("com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0")
implementation("com.github.bumptech.glide:glide:4.15.1")

View file

@ -64,7 +64,10 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
override fun onPause() {
super.onPause()
if (preferences.biometricLock.value == BiometricLock.ENABLED) {
if (
preferences.biometricLock.value == BiometricLock.ENABLED &&
notallyXApplication.locked.value
) {
hide()
}
}

View file

@ -1,7 +1,6 @@
package com.philkes.notallyx.utils
import android.app.Activity
import android.app.KeyguardManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
@ -12,7 +11,6 @@ import android.content.ContentResolver
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.hardware.biometrics.BiometricManager
import android.net.Uri
@ -126,30 +124,15 @@ fun Context.getFileName(uri: Uri): String? =
}
fun Context.canAuthenticateWithBiometrics(): Int {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
val keyguardManager = ContextCompat.getSystemService(this, KeyguardManager::class.java)
val packageManager: PackageManager = this.packageManager
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
}
if (keyguardManager?.isKeyguardSecure == false) {
return BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
}
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
val biometricManager: BiometricManager =
this.getSystemService(BiometricManager::class.java)
return biometricManager.canAuthenticate()
val biometricManager = androidx.biometric.BiometricManager.from(this)
val authenticators =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG or
androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
} else {
val biometricManager: BiometricManager =
this.getSystemService(BiometricManager::class.java)
return biometricManager.canAuthenticate(
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
}
}
return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
return biometricManager.canAuthenticate(authenticators)
}
fun Context.getUriForFile(file: File): Uri =

View file

@ -4,15 +4,13 @@ import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.BiometricPrompt
import android.hardware.fingerprint.FingerprintManager
import android.os.Build
import android.os.CancellationSignal
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.philkes.notallyx.R
import javax.crypto.Cipher
@ -27,7 +25,7 @@ fun Activity.showBiometricOrPinPrompt(
) {
showBiometricOrPinPrompt(
isForDecrypt,
this,
this as FragmentActivity,
activityResultLauncher,
titleResId,
descriptionResId,
@ -48,7 +46,7 @@ fun Fragment.showBiometricOrPinPrompt(
) {
showBiometricOrPinPrompt(
isForDecrypt,
requireContext(),
activity!!,
activityResultLauncher,
titleResId,
descriptionResId,
@ -60,7 +58,7 @@ fun Fragment.showBiometricOrPinPrompt(
private fun showBiometricOrPinPrompt(
isForDecrypt: Boolean,
context: Context,
context: FragmentActivity,
activityResultLauncher: ActivityResultLauncher<Intent>,
titleResId: Int,
descriptionResId: Int? = null,
@ -69,142 +67,55 @@ private fun showBiometricOrPinPrompt(
onFailure: (errorCode: Int?) -> Unit,
) {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
// Android 11+ with BiometricPrompt and Authenticators
val prompt =
BiometricPrompt.Builder(context)
.apply {
setTitle(context.getString(titleResId))
descriptionResId?.let {
setDescription(context.getString(descriptionResId))
}
setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
}
.build()
val cipher =
if (isForDecrypt) {
getInitializedCipherForDecryption(iv = cipherIv!!)
} else {
getInitializedCipherForEncryption()
}
prompt.authenticate(
BiometricPrompt.CryptoObject(cipher),
getCancellationSignal(context),
context.mainExecutor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult?
) {
super.onAuthenticationSucceeded(result)
onSuccess.invoke(result!!.cryptoObject!!.cipher)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
onFailure.invoke(null)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
super.onAuthenticationError(errorCode, errString)
onFailure.invoke(errorCode)
}
},
)
}
Build.VERSION.SDK_INT == Build.VERSION_CODES.Q -> {
// Android 10: Use BiometricPrompt without Authenticators
val prompt =
BiometricPrompt.Builder(context)
.apply {
setTitle(context.getString(titleResId))
descriptionResId?.let {
setDescription(context.getString(descriptionResId))
}
setNegativeButton(
context.getString(R.string.cancel),
context.mainExecutor,
) { _, _ ->
onFailure.invoke(null)
}
}
.build()
val cipher =
if (isForDecrypt) {
getInitializedCipherForDecryption(iv = cipherIv!!)
} else {
getInitializedCipherForEncryption()
}
prompt.authenticate(
BiometricPrompt.CryptoObject(cipher),
getCancellationSignal(context),
context.mainExecutor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult?
) {
super.onAuthenticationSucceeded(result)
onSuccess.invoke(result!!.cryptoObject!!.cipher)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
onFailure.invoke(null)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
super.onAuthenticationError(errorCode, errString)
onFailure.invoke(errorCode)
}
},
)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
val fingerprintManager =
ContextCompat.getSystemService(context, FingerprintManager::class.java)
if (
fingerprintManager?.isHardwareDetected == true &&
fingerprintManager.hasEnrolledFingerprints()
) {
val cipher =
if (isForDecrypt) {
getInitializedCipherForDecryption(iv = cipherIv!!)
} else {
getInitializedCipherForEncryption()
val promptInfo =
BiometricPrompt.PromptInfo.Builder()
.apply {
setTitle(context.getString(titleResId))
descriptionResId?.let {
setDescription(context.getString(descriptionResId))
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
} else {
setNegativeButtonText(context.getString(R.string.cancel))
setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
)
}
}
.build()
val cipher =
if (isForDecrypt) {
getInitializedCipherForDecryption(iv = cipherIv!!)
} else {
getInitializedCipherForEncryption()
}
val authCallback =
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
onSuccess.invoke(result.cryptoObject!!.cipher!!)
}
fingerprintManager.authenticate(
FingerprintManager.CryptoObject(cipher),
getCancellationSignal(context),
0,
object : FingerprintManager.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: FingerprintManager.AuthenticationResult?
) {
super.onAuthenticationSucceeded(result)
onSuccess.invoke(result!!.cryptoObject!!.cipher)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
onFailure.invoke(null)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
onFailure.invoke(null)
}
override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence?,
) {
super.onAuthenticationError(errorCode, errString)
onFailure.invoke(errorCode)
}
},
null,
)
} else {
promptPinAuthentication(context, activityResultLauncher, titleResId, onFailure)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
onFailure.invoke(errorCode)
}
}
val prompt =
BiometricPrompt(context, ContextCompat.getMainExecutor(context), authCallback)
prompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
else -> {
@ -214,14 +125,6 @@ private fun showBiometricOrPinPrompt(
}
}
private fun getCancellationSignal(context: Context): CancellationSignal {
return CancellationSignal().apply {
setOnCancelListener {
Toast.makeText(context, R.string.biometrics_failure, Toast.LENGTH_SHORT).show()
}
}
}
private fun promptPinAuthentication(
context: Context,
activityResultLauncher: ActivityResultLauncher<Intent>,