Use androidx.biometric to fix compatibility issues

This commit is contained in:
PhilKes 2025-05-05 18:33:03 +02:00 committed by Phil
parent 2341c30586
commit 0fee25f022
3 changed files with 59 additions and 173 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

@ -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,19 +67,25 @@ 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)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
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 =
@ -90,16 +94,13 @@ private fun showBiometricOrPinPrompt(
} else {
getInitializedCipherForEncryption()
}
prompt.authenticate(
BiometricPrompt.CryptoObject(cipher),
getCancellationSignal(context),
context.mainExecutor,
val authCallback =
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult?
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
onSuccess.invoke(result!!.cryptoObject!!.cipher)
onSuccess.invoke(result.cryptoObject!!.cipher!!)
}
override fun onAuthenticationFailed() {
@ -107,104 +108,14 @@ private fun showBiometricOrPinPrompt(
onFailure.invoke(null)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
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()
}
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 onAuthenticationError(
errorCode: Int,
errString: CharSequence?,
) {
super.onAuthenticationError(errorCode, errString)
onFailure.invoke(errorCode)
}
},
null,
)
} else {
promptPinAuthentication(context, activityResultLauncher, titleResId, onFailure)
}
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>,