diff --git a/build.gradle b/build.gradle
index 4933627f..96683fd5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -71,6 +71,8 @@ ext {
sqlCipherVersion = "4.4.0"
+ androidXBiometricVersion = "1.0.1"
+
/* JavaFX */
diff --git a/ui/BankingAndroidApp/build.gradle b/ui/BankingAndroidApp/build.gradle
index 70c10faa..7a90c36c 100644
--- a/ui/BankingAndroidApp/build.gradle
+++ b/ui/BankingAndroidApp/build.gradle
@@ -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"
diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/activities/LoginActivity.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/activities/LoginActivity.kt
index edfec304..779bbf5d 100644
--- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/activities/LoginActivity.kt
+++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/activities/LoginActivity.kt
@@ -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,12 +41,19 @@ open class LoginActivity : BaseActivity() {
lytPasswordAuthentication.visibility = View.GONE
btnBiometricAuthentication.authenticationSuccessful = { biometricAuthenticationSuccessful() }
+
+ btnBiometricAuthentication.showBiometricPrompt()
}
}
protected open fun checkEnteredPasswordAndLogIn() {
- logIn()
+ 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() {
diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/AuthenticationResult.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/AuthenticationResult.kt
new file mode 100644
index 00000000..d6c8111d
--- /dev/null
+++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/AuthenticationResult.kt
@@ -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"
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/AuthenticationService.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/AuthenticationService.kt
index b22ddab0..3087cecc 100644
--- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/AuthenticationService.kt
+++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/AuthenticationService.kt
@@ -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
diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/BiometricAuthenticationService.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/BiometricAuthenticationService.kt
new file mode 100644
index 00000000..f7cdc4a1
--- /dev/null
+++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/BiometricAuthenticationService.kt
@@ -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)
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/IBiometricAuthenticationService.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/IBiometricAuthenticationService.kt
new file mode 100644
index 00000000..0c32d3b2
--- /dev/null
+++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/authentication/IBiometricAuthenticationService.kt
@@ -0,0 +1,11 @@
+package net.dankito.banking.ui.android.authentication
+
+
+interface IBiometricAuthenticationService {
+
+ val supportsBiometricAuthentication: Boolean
+
+
+ fun authenticate(authenticationResult: (AuthenticationResult) -> Unit)
+
+}
\ No newline at end of file
diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingComponent.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingComponent.kt
index 97d571b3..961be4ea 100644
--- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingComponent.kt
+++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingComponent.kt
@@ -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)
}
\ No newline at end of file
diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingModule.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingModule.kt
index ba920e4f..fe0c739b 100644
--- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingModule.kt
+++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingModule.kt
@@ -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)
}
diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/dialogs/settings/ProtectAppSettingsDialog.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/dialogs/settings/ProtectAppSettingsDialog.kt
index c747f664..421efe0c 100644
--- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/dialogs/settings/ProtectAppSettingsDialog.kt
+++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/dialogs/settings/ProtectAppSettingsDialog.kt
@@ -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 {
diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/views/BiometricAuthenticationButton.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/views/BiometricAuthenticationButton.kt
index 61c8ee38..e0d9538c 100644
--- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/views/BiometricAuthenticationButton.kt
+++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/views/BiometricAuthenticationButton.kt
@@ -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() {
- authenticationSuccessful?.invoke()
+ biometricAuthenticationService.authenticate { result ->
+ if (result.successful) {
+ authenticationSuccessful?.invoke()
+ }
+ }
+ }
+
+
+ open fun showBiometricPrompt() {
+ doBiometricAuthenticationAndLogIn()
}
}
\ No newline at end of file
diff --git a/ui/BankingAndroidApp/src/main/res/drawable/ic_baseline_fingerprint_24.xml b/ui/BankingAndroidApp/src/main/res/drawable/ic_baseline_fingerprint_24.xml
new file mode 100644
index 00000000..4ad0310f
--- /dev/null
+++ b/ui/BankingAndroidApp/src/main/res/drawable/ic_baseline_fingerprint_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/ui/BankingAndroidApp/src/main/res/values-de/strings.xml b/ui/BankingAndroidApp/src/main/res/values-de/strings.xml
index 45649184..a8f95bd4 100644
--- a/ui/BankingAndroidApp/src/main/res/values-de/strings.xml
+++ b/ui/BankingAndroidApp/src/main/res/values-de/strings.xml
@@ -44,7 +44,8 @@
Geben Sie Ihr Passwort ein
Passwort eingeben
Anmelden
- Mit Fingerabdruck oder Gesichtserkennung authentifizieren
+ Authentifizieren Sie sich per Fingerabdruck oder Gesichtserkennung
+ Falsches Passwort eingegeben
Seitenleiste öffnen
diff --git a/ui/BankingAndroidApp/src/main/res/values/strings.xml b/ui/BankingAndroidApp/src/main/res/values/strings.xml
index 1e0c6342..8ba09c3e 100644
--- a/ui/BankingAndroidApp/src/main/res/values/strings.xml
+++ b/ui/BankingAndroidApp/src/main/res/values/strings.xml
@@ -45,6 +45,7 @@
Enter your password
Login
Authenticate with fingerprint or face recognition
+ Incorrect password entered
Open navigation drawer