Skip to content

Commit

Permalink
PreferenceBaseActivity: Add support for legacy device credential auth
Browse files Browse the repository at this point in the history
Although androidx.biometric can handle biometric auth on legacy Android
versions, we're intentionally not using it. The legacy device credential
API does both biometric and device credential auth and it is not
possible to disable just the biometric part, so just let the legacy API
handle everything.

Issue: #93

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
  • Loading branch information
chenxiaolong committed Oct 25, 2024
1 parent 7935f95 commit 7d1a280
Showing 1 changed file with 83 additions and 28 deletions.
111 changes: 83 additions & 28 deletions app/src/main/java/com/chiller3/rsaf/PreferenceBaseActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package com.chiller3.rsaf

import android.app.ActivityManager
import android.app.KeyguardManager
import android.content.Intent
import android.os.Build
import android.os.Bundle
Expand All @@ -16,6 +17,7 @@ import android.view.ViewGroup
import android.view.WindowManager
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager.Authenticators
import androidx.biometric.BiometricPrompt
Expand All @@ -32,20 +34,33 @@ abstract class PreferenceBaseActivity : AppCompatActivity() {
// within the app.
private var bioAuthenticated = false
private var lastPause = 0L

private fun supportsModernDeviceCredential() =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
}

protected abstract val actionBarTitle: CharSequence?

protected abstract val showUpButton: Boolean

protected abstract fun createFragment(): PreferenceBaseFragment

private val tag = javaClass.simpleName

private lateinit var prefs: Preferences
private lateinit var bioPrompt: BiometricPrompt
private lateinit var activityManager: ActivityManager
private var isCoveredBySafeActivity = false

protected abstract val actionBarTitle: CharSequence?

protected abstract val showUpButton: Boolean

protected abstract fun createFragment(): PreferenceBaseFragment
private val requestLegacyDeviceCredential =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
onAuthenticationSucceeded()
} else {
// We can't know the reason.
onAuthenticationFailed()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
Expand Down Expand Up @@ -107,28 +122,14 @@ abstract class PreferenceBaseActivity : AppCompatActivity() {
this,
mainExecutor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
Toast.makeText(
this@PreferenceBaseActivity,
getString(R.string.biometric_error, errString),
Toast.LENGTH_LONG,
).show()
finish()
}

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
bioAuthenticated = true
refreshGlobalVisibility()
}

override fun onAuthenticationFailed() {
Toast.makeText(
this@PreferenceBaseActivity,
R.string.biometric_failure,
Toast.LENGTH_LONG,
).show()
finish()
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) =
this@PreferenceBaseActivity.onAuthenticationError(errorCode, errString)

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) =
this@PreferenceBaseActivity.onAuthenticationSucceeded()

override fun onAuthenticationFailed() =
this@PreferenceBaseActivity.onAuthenticationFailed()
},
)

Expand Down Expand Up @@ -158,7 +159,7 @@ abstract class PreferenceBaseActivity : AppCompatActivity() {
if (!prefs.requireAuth) {
bioAuthenticated = true
} else {
startBiometricAuth()
startAuth()
}
}

Expand Down Expand Up @@ -214,6 +215,14 @@ abstract class PreferenceBaseActivity : AppCompatActivity() {
super.onWindowAttributesChanged(params)
}

private fun startAuth() {
if (supportsModernDeviceCredential()) {
startBiometricAuth()
} else {
startLegacyDeviceCredentialAuth()
}
}

private fun startBiometricAuth() {
Log.d(tag, "Starting biometric authentication")

Expand All @@ -225,6 +234,52 @@ abstract class PreferenceBaseActivity : AppCompatActivity() {
bioPrompt.authenticate(promptInfo)
}


private fun startLegacyDeviceCredentialAuth() {
Log.d(tag, "Starting legacy device credential authentication")

val keyGuardManager = getSystemService(KeyguardManager::class.java)
@Suppress("DEPRECATION")
val intent = keyGuardManager?.createConfirmDeviceCredentialIntent(
getString(R.string.biometric_title),
"",
)

if (intent != null) {
requestLegacyDeviceCredential.launch(intent)
} else {
onAuthenticationFailed()
}
}

fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
if (!supportsModernDeviceCredential() && errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
startLegacyDeviceCredentialAuth()
return
}

Toast.makeText(
this@PreferenceBaseActivity,
getString(R.string.biometric_error, errString),
Toast.LENGTH_LONG,
).show()
finish()
}

private fun onAuthenticationSucceeded() {
bioAuthenticated = true
refreshGlobalVisibility()
}

private fun onAuthenticationFailed() {
Toast.makeText(
this@PreferenceBaseActivity,
R.string.biometric_failure,
Toast.LENGTH_LONG,
).show()
finish()
}

private fun refreshTaskState() {
// This is an awful hack, but we need it to be able to only apply the view hiding in the
// topmost activity to ensure that predictive back gestures still work.
Expand Down

0 comments on commit 7d1a280

Please sign in to comment.