From 405b897754ce4961c708181f732af0de31db140a Mon Sep 17 00:00:00 2001 From: BinTianqi Date: Wed, 15 May 2024 13:48:37 +0800 Subject: [PATCH] support using biometrics to auth --- .../main/java/com/bintianqi/owndroid/Auth.kt | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/bintianqi/owndroid/Auth.kt b/app/src/main/java/com/bintianqi/owndroid/Auth.kt index 686f77e..6075013 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Auth.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Auth.kt @@ -10,6 +10,7 @@ import android.widget.Toast import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt.AuthenticationCallback +import androidx.biometric.BiometricPrompt.PromptInfo import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -22,12 +23,15 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalContext import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import com.bintianqi.owndroid.ui.theme.OwnDroidTheme +import kotlinx.coroutines.* class AuthFragment: Fragment() { + @OptIn(DelicateCoroutinesApi::class) @SuppressLint("UnrememberedMutableState") override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val sharedPref = context?.getSharedPreferences("data", Context.MODE_PRIVATE)!! @@ -40,6 +44,11 @@ class AuthFragment: Fragment() { fragmentTransaction.commit() } } + val promptInfo = PromptInfo.Builder() + .setTitle("Auth") + .setSubtitle("Auth OwnDroid with password or biometric") + .setConfirmationRequired(true) + var fallback = false val callback = object: AuthenticationCallback() { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) @@ -48,16 +57,32 @@ class AuthFragment: Fragment() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) if(errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL) onAuthSucceed() + if(errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) fallback = true if(errorCode == BiometricPrompt.ERROR_CANCELED) return Toast.makeText(context, errString, Toast.LENGTH_SHORT).show() } } + GlobalScope.launch(Dispatchers.Main) { + while(true){ + if(fallback){ + val fallbackPromptInfo = PromptInfo.Builder() + .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL) + .setTitle("Auth") + .setSubtitle("Auth OwnDroid with password") + .setConfirmationRequired(true) + .build() + authWithBiometricPrompt(requireActivity(), fallbackPromptInfo, callback) + break + } + delay(50) + } + } return ComposeView(requireContext()).apply { setContent { val materialYou = mutableStateOf(sharedPref.getBoolean("material_you",true)) val blackTheme = mutableStateOf(sharedPref.getBoolean("black_theme", false)) OwnDroidTheme(materialYou.value, blackTheme.value) { - Auth(this@AuthFragment.requireActivity(), callback) + Auth(this@AuthFragment.requireActivity(), callback, promptInfo) } } } @@ -65,7 +90,8 @@ class AuthFragment: Fragment() { } @Composable -fun Auth(activity: FragmentActivity, callback: AuthenticationCallback) { +fun Auth(activity: FragmentActivity, callback: AuthenticationCallback, promptInfo: PromptInfo.Builder) { + val context = LocalContext.current Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, @@ -74,7 +100,19 @@ fun Auth(activity: FragmentActivity, callback: AuthenticationCallback) { Text(text = "Authenticate", style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onBackground) Button( onClick = { - authWithBiometricPrompt(activity, callback) + val bioManager = BiometricManager.from(context) + when(BiometricManager.BIOMETRIC_SUCCESS){ + bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) -> + promptInfo + .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG) + .setNegativeButtonText("Use password") + bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) -> + promptInfo + .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK) + .setNegativeButtonText("Use password") + else -> promptInfo.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL) + } + authWithBiometricPrompt(activity, promptInfo.build(), callback) } ){ Text(text = "Start") @@ -82,14 +120,8 @@ fun Auth(activity: FragmentActivity, callback: AuthenticationCallback) { } } -private fun authWithBiometricPrompt(activity: FragmentActivity, callback: AuthenticationCallback) { +private fun authWithBiometricPrompt(activity: FragmentActivity, promptInfo: PromptInfo, callback: AuthenticationCallback) { val executor = ContextCompat.getMainExecutor(activity.applicationContext) val biometricPrompt = BiometricPrompt(activity, executor, callback) - val promptInfo = BiometricPrompt.PromptInfo.Builder() - .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL) - .setTitle("Auth") - .setConfirmationRequired(true) - .setSubtitle("Enter password") - .build() biometricPrompt.authenticate(promptInfo) }