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