mirror of
https://github.com/PhilKes/NotallyX.git
synced 2025-06-28 20:29:54 +00:00
Merge pull request #573 from PhilKes/fix/biometric-lock-open-note
Fix/biometric lock open note
This commit is contained in:
commit
fb35ffdac4
4 changed files with 63 additions and 174 deletions
|
@ -190,7 +190,7 @@ dependencies {
|
||||||
implementation("androidx.security:security-crypto:1.1.0-alpha06")
|
implementation("androidx.security:security-crypto:1.1.0-alpha06")
|
||||||
implementation("androidx.sqlite:sqlite-ktx:2.4.0")
|
implementation("androidx.sqlite:sqlite-ktx:2.4.0")
|
||||||
implementation("androidx.work:work-runtime:2.9.1")
|
implementation("androidx.work:work-runtime:2.9.1")
|
||||||
|
implementation("androidx.biometric:biometric:1.1.0")
|
||||||
implementation("cat.ereza:customactivityoncrash:2.4.0")
|
implementation("cat.ereza:customactivityoncrash:2.4.0")
|
||||||
implementation("com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0")
|
implementation("com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0")
|
||||||
implementation("com.github.bumptech.glide:glide:4.15.1")
|
implementation("com.github.bumptech.glide:glide:4.15.1")
|
||||||
|
|
|
@ -64,7 +64,10 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
if (preferences.biometricLock.value == BiometricLock.ENABLED) {
|
if (
|
||||||
|
preferences.biometricLock.value == BiometricLock.ENABLED &&
|
||||||
|
notallyXApplication.locked.value
|
||||||
|
) {
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.philkes.notallyx.utils
|
package com.philkes.notallyx.utils
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.KeyguardManager
|
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
@ -12,7 +11,6 @@ import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.hardware.biometrics.BiometricManager
|
import android.hardware.biometrics.BiometricManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -126,30 +124,15 @@ fun Context.getFileName(uri: Uri): String? =
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.canAuthenticateWithBiometrics(): Int {
|
fun Context.canAuthenticateWithBiometrics(): Int {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val biometricManager = androidx.biometric.BiometricManager.from(this)
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
val authenticators =
|
||||||
val keyguardManager = ContextCompat.getSystemService(this, KeyguardManager::class.java)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
val packageManager: PackageManager = this.packageManager
|
androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
||||||
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
|
androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
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()
|
|
||||||
} else {
|
} else {
|
||||||
val biometricManager: BiometricManager =
|
androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
this.getSystemService(BiometricManager::class.java)
|
|
||||||
return biometricManager.canAuthenticate(
|
|
||||||
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
|
||||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
return biometricManager.canAuthenticate(authenticators)
|
||||||
return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getUriForFile(file: File): Uri =
|
fun Context.getUriForFile(file: File): Uri =
|
||||||
|
|
|
@ -4,15 +4,13 @@ import android.app.Activity
|
||||||
import android.app.KeyguardManager
|
import android.app.KeyguardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
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.Build
|
||||||
import android.os.CancellationSignal
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
|
|
||||||
|
@ -27,7 +25,7 @@ fun Activity.showBiometricOrPinPrompt(
|
||||||
) {
|
) {
|
||||||
showBiometricOrPinPrompt(
|
showBiometricOrPinPrompt(
|
||||||
isForDecrypt,
|
isForDecrypt,
|
||||||
this,
|
this as FragmentActivity,
|
||||||
activityResultLauncher,
|
activityResultLauncher,
|
||||||
titleResId,
|
titleResId,
|
||||||
descriptionResId,
|
descriptionResId,
|
||||||
|
@ -48,7 +46,7 @@ fun Fragment.showBiometricOrPinPrompt(
|
||||||
) {
|
) {
|
||||||
showBiometricOrPinPrompt(
|
showBiometricOrPinPrompt(
|
||||||
isForDecrypt,
|
isForDecrypt,
|
||||||
requireContext(),
|
activity!!,
|
||||||
activityResultLauncher,
|
activityResultLauncher,
|
||||||
titleResId,
|
titleResId,
|
||||||
descriptionResId,
|
descriptionResId,
|
||||||
|
@ -60,7 +58,7 @@ fun Fragment.showBiometricOrPinPrompt(
|
||||||
|
|
||||||
private fun showBiometricOrPinPrompt(
|
private fun showBiometricOrPinPrompt(
|
||||||
isForDecrypt: Boolean,
|
isForDecrypt: Boolean,
|
||||||
context: Context,
|
context: FragmentActivity,
|
||||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||||
titleResId: Int,
|
titleResId: Int,
|
||||||
descriptionResId: Int? = null,
|
descriptionResId: Int? = null,
|
||||||
|
@ -69,142 +67,55 @@ private fun showBiometricOrPinPrompt(
|
||||||
onFailure: (errorCode: Int?) -> Unit,
|
onFailure: (errorCode: Int?) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
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 -> {
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
|
||||||
val fingerprintManager =
|
val promptInfo =
|
||||||
ContextCompat.getSystemService(context, FingerprintManager::class.java)
|
BiometricPrompt.PromptInfo.Builder()
|
||||||
if (
|
.apply {
|
||||||
fingerprintManager?.isHardwareDetected == true &&
|
setTitle(context.getString(titleResId))
|
||||||
fingerprintManager.hasEnrolledFingerprints()
|
descriptionResId?.let {
|
||||||
) {
|
setDescription(context.getString(descriptionResId))
|
||||||
val cipher =
|
}
|
||||||
if (isForDecrypt) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
getInitializedCipherForDecryption(iv = cipherIv!!)
|
setAllowedAuthenticators(
|
||||||
} else {
|
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
||||||
getInitializedCipherForEncryption()
|
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() {
|
override fun onAuthenticationFailed() {
|
||||||
super.onAuthenticationFailed()
|
super.onAuthenticationFailed()
|
||||||
onFailure.invoke(null)
|
onFailure.invoke(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAuthenticationError(
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
errorCode: Int,
|
super.onAuthenticationError(errorCode, errString)
|
||||||
errString: CharSequence?,
|
onFailure.invoke(errorCode)
|
||||||
) {
|
}
|
||||||
super.onAuthenticationError(errorCode, errString)
|
}
|
||||||
onFailure.invoke(errorCode)
|
val prompt =
|
||||||
}
|
BiometricPrompt(context, ContextCompat.getMainExecutor(context), authCallback)
|
||||||
},
|
prompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
|
||||||
null,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
promptPinAuthentication(context, activityResultLauncher, titleResId, onFailure)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
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(
|
private fun promptPinAuthentication(
|
||||||
context: Context,
|
context: Context,
|
||||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue