Added biometric authentication dialog / prompt
This commit is contained in:
parent
269ad0bb33
commit
b82d986da2
|
@ -71,6 +71,8 @@ ext {
|
|||
|
||||
sqlCipherVersion = "4.4.0"
|
||||
|
||||
androidXBiometricVersion = "1.0.1"
|
||||
|
||||
|
||||
/* JavaFX */
|
||||
|
||||
|
|
|
@ -130,6 +130,8 @@ dependencies {
|
|||
implementation "com.google.android.material:material:$materialComponentsVersion"
|
||||
implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion"
|
||||
|
||||
implementation "androidx.biometric:biometric:$androidXBiometricVersion"
|
||||
|
||||
implementation "com.mikepenz:fastadapter:$fastAdapterVersion"
|
||||
implementation "com.mikepenz:fastadapter-extensions-binding:$fastAdapterVersion"
|
||||
implementation "com.mikepenz:fastadapter-extensions-drag:$fastAdapterVersion"
|
||||
|
|
|
@ -32,9 +32,7 @@ open class LoginActivity : BaseActivity() {
|
|||
protected open fun initUi() {
|
||||
setContentView(R.layout.activity_login)
|
||||
|
||||
val authenticationType = authenticationService.getAuthenticationType()
|
||||
|
||||
if (authenticationType == AuthenticationType.Password) {
|
||||
if (authenticationService.authenticationType == AuthenticationType.Password) {
|
||||
lytBiometricAuthentication.visibility = View.GONE
|
||||
|
||||
btnLogin.setOnClickListener { checkEnteredPasswordAndLogIn() }
|
||||
|
@ -43,13 +41,20 @@ open class LoginActivity : BaseActivity() {
|
|||
lytPasswordAuthentication.visibility = View.GONE
|
||||
|
||||
btnBiometricAuthentication.authenticationSuccessful = { biometricAuthenticationSuccessful() }
|
||||
|
||||
btnBiometricAuthentication.showBiometricPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected open fun checkEnteredPasswordAndLogIn() {
|
||||
if (authenticationService.isCorrectUserPassword(edtxtLoginPassword.text)) {
|
||||
logIn()
|
||||
}
|
||||
else {
|
||||
Toast.makeText(this, R.string.activity_login_incorrect_password_entered, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun biometricAuthenticationSuccessful() {
|
||||
logIn()
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package net.dankito.banking.ui.android.authentication
|
||||
|
||||
|
||||
open class AuthenticationResult(
|
||||
open val successful: Boolean,
|
||||
open val error: String? = null
|
||||
) {
|
||||
|
||||
override fun toString(): String {
|
||||
return if (successful) {
|
||||
"Successful"
|
||||
}
|
||||
else {
|
||||
"Error occurred: $error"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -5,12 +5,19 @@ import net.dankito.utils.multiplatform.File
|
|||
|
||||
|
||||
open class AuthenticationService(
|
||||
protected val biometricAuthenticationService: IBiometricAuthenticationService,
|
||||
protected val dataFolder: File,
|
||||
protected val serializer: ISerializer
|
||||
) {
|
||||
|
||||
open val isBiometricAuthenticationSupported: Boolean
|
||||
get() = true
|
||||
get() = biometricAuthenticationService.supportsBiometricAuthentication
|
||||
|
||||
open var authenticationType: AuthenticationType = AuthenticationType.None
|
||||
protected set
|
||||
|
||||
|
||||
fun setAuthenticationMethodToBiometric() {
|
||||
|
||||
open fun getAuthenticationType(): AuthenticationType {
|
||||
return AuthenticationType.None
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package net.dankito.banking.ui.android.authentication
|
||||
|
||||
import android.content.Context
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import net.dankito.banking.ui.android.R
|
||||
import net.dankito.banking.ui.android.util.CurrentActivityTracker
|
||||
|
||||
|
||||
open class BiometricAuthenticationService(
|
||||
protected open val context: Context,
|
||||
protected open val activityTracker: CurrentActivityTracker,
|
||||
protected open val biometricManager: BiometricManager = BiometricManager.from(context)
|
||||
) : IBiometricAuthenticationService {
|
||||
|
||||
override val supportsBiometricAuthentication: Boolean
|
||||
get() = biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|
||||
|
||||
override fun authenticate(authenticationResult: (AuthenticationResult) -> Unit) {
|
||||
activityTracker.currentOrNextActivity { activity ->
|
||||
val executor = ContextCompat.getMainExecutor(context)
|
||||
|
||||
val biometricPrompt = BiometricPrompt(activity, executor,
|
||||
object : BiometricPrompt.AuthenticationCallback() {
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errorString: CharSequence) {
|
||||
super.onAuthenticationError(errorCode, errorString)
|
||||
authenticationResult(AuthenticationResult(false, errorString.toString()))
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
|
||||
authenticationResult(AuthenticationResult(true))
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed()
|
||||
authenticationResult(AuthenticationResult(false))
|
||||
}
|
||||
})
|
||||
|
||||
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(context.getString(R.string.activity_login_authenticate_with_biometrics_prompt))
|
||||
.setDeviceCredentialAllowed(true)
|
||||
.build()
|
||||
|
||||
biometricPrompt.authenticate(promptInfo)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package net.dankito.banking.ui.android.authentication
|
||||
|
||||
|
||||
interface IBiometricAuthenticationService {
|
||||
|
||||
val supportsBiometricAuthentication: Boolean
|
||||
|
||||
|
||||
fun authenticate(authenticationResult: (AuthenticationResult) -> Unit)
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ import net.dankito.banking.ui.android.dialogs.TransferMoneyDialog
|
|||
import net.dankito.banking.ui.android.dialogs.settings.ProtectAppSettingsDialog
|
||||
import net.dankito.banking.ui.android.dialogs.settings.SettingsDialogBase
|
||||
import net.dankito.banking.ui.android.home.HomeFragment
|
||||
import net.dankito.banking.ui.android.views.BiometricAuthenticationButton
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
|
@ -44,6 +45,8 @@ interface BankingComponent {
|
|||
|
||||
fun inject(protectAppSettingsDialog: ProtectAppSettingsDialog)
|
||||
|
||||
fun inject(biometricAuthenticationButton: BiometricAuthenticationButton)
|
||||
|
||||
fun inject(sendMessageLogDialog: SendMessageLogDialog)
|
||||
|
||||
}
|
|
@ -18,6 +18,8 @@ import net.dankito.banking.bankfinder.LuceneBankFinder
|
|||
import net.dankito.banking.persistence.RoomBankingPersistence
|
||||
import net.dankito.banking.persistence.model.RoomModelCreator
|
||||
import net.dankito.banking.ui.android.authentication.AuthenticationService
|
||||
import net.dankito.banking.ui.android.authentication.BiometricAuthenticationService
|
||||
import net.dankito.banking.ui.android.authentication.IBiometricAuthenticationService
|
||||
import net.dankito.banking.ui.model.mapper.IModelCreator
|
||||
import net.dankito.banking.ui.util.CurrencyInfoProvider
|
||||
import net.dankito.utils.multiplatform.toFile
|
||||
|
@ -87,8 +89,14 @@ class BankingModule(private val applicationContext: Context) {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthenticationService(@Named(DataFolderKey) dataFolder: File, serializer: ISerializer) : AuthenticationService {
|
||||
return AuthenticationService(dataFolder, serializer)
|
||||
fun provideAuthenticationService(biometricAuthenticationService: IBiometricAuthenticationService, @Named(DataFolderKey) dataFolder: File, serializer: ISerializer) : AuthenticationService {
|
||||
return AuthenticationService(biometricAuthenticationService, dataFolder, serializer)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBiometricAuthenticationService(context: Context, currentActivityTracker: CurrentActivityTracker) : IBiometricAuthenticationService {
|
||||
return BiometricAuthenticationService(context, currentActivityTracker)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -57,7 +57,6 @@ open class ProtectAppSettingsDialog : SettingsDialogBase() {
|
|||
setupToolbar(this, context.getString(R.string.settings), false)
|
||||
}
|
||||
|
||||
val authenticationType = authenticationService.getAuthenticationType()
|
||||
val isBiometricAuthenticationSupported = authenticationService.isBiometricAuthenticationSupported
|
||||
|
||||
segmentedGroup.doOnNextLayout {
|
||||
|
@ -94,7 +93,7 @@ open class ProtectAppSettingsDialog : SettingsDialogBase() {
|
|||
|
||||
btnSetAuthenticationMethod.setOnClickListener { setAuthenticationMethod() }
|
||||
|
||||
if (authenticationService.isBiometricAuthenticationSupported && authenticationType == AuthenticationType.Biometric) {
|
||||
if (isBiometricAuthenticationSupported && authenticationService.authenticationType == AuthenticationType.Biometric) {
|
||||
btnShowBiometricAuthenticationSection.isChecked = true
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -6,17 +6,25 @@ import android.view.LayoutInflater
|
|||
import android.widget.LinearLayout
|
||||
import kotlinx.android.synthetic.main.view_biometric_authentication_button.view.*
|
||||
import net.dankito.banking.ui.android.R
|
||||
import net.dankito.banking.ui.android.authentication.IBiometricAuthenticationService
|
||||
import net.dankito.banking.ui.android.di.BankingComponent
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
open class BiometricAuthenticationButton @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
@Inject
|
||||
protected lateinit var biometricAuthenticationService: IBiometricAuthenticationService
|
||||
|
||||
|
||||
open var authenticationSuccessful: (() -> Unit)? = null
|
||||
|
||||
|
||||
init {
|
||||
BankingComponent.component.inject(this)
|
||||
|
||||
setupUi(context)
|
||||
}
|
||||
|
||||
|
@ -30,7 +38,16 @@ open class BiometricAuthenticationButton @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
protected open fun doBiometricAuthenticationAndLogIn() {
|
||||
biometricAuthenticationService.authenticate { result ->
|
||||
if (result.successful) {
|
||||
authenticationSuccessful?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open fun showBiometricPrompt() {
|
||||
doBiometricAuthenticationAndLogIn()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39 -2.57,0 -4.66,1.97 -4.66,4.39 0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94 1.7,0 3.08,1.32 3.08,2.94 0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z"/>
|
||||
</vector>
|
|
@ -44,7 +44,8 @@
|
|||
<string name="activity_login_authenticate_with_password_prompt">Geben Sie Ihr Passwort ein</string>
|
||||
<string name="activity_login_authenticate_with_password_hint">Passwort eingeben</string>
|
||||
<string name="activity_login_login_button_title">Anmelden</string>
|
||||
<string name="activity_login_authenticate_with_biometrics_prompt">Mit Fingerabdruck oder Gesichtserkennung authentifizieren</string>
|
||||
<string name="activity_login_authenticate_with_biometrics_prompt">Authentifizieren Sie sich per Fingerabdruck oder Gesichtserkennung</string>
|
||||
<string name="activity_login_incorrect_password_entered">Falsches Passwort eingegeben</string>
|
||||
|
||||
|
||||
<string name="navigation_drawer_open">Seitenleiste öffnen</string>
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
<string name="activity_login_authenticate_with_password_hint">Enter your password</string>
|
||||
<string name="activity_login_login_button_title">Login</string>
|
||||
<string name="activity_login_authenticate_with_biometrics_prompt">Authenticate with fingerprint or face recognition</string>
|
||||
<string name="activity_login_incorrect_password_entered">Incorrect password entered</string>
|
||||
|
||||
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
|
|
Loading…
Reference in New Issue