diff --git a/CHANGELOG.md b/CHANGELOG.md index a4623fb647b..1dfbd636889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ ownCloud admins and users. ## Summary * Bugfix - Confusing behaviour when creating new files using apps provider: [#4560](https://github.com/owncloud/android/issues/4560) +* Bugfix - App crashes at start when biometrics fail: [#7134](https://github.com/owncloud/enterprise/issues/7134) ## Details @@ -47,6 +48,15 @@ ownCloud admins and users. https://github.com/owncloud/android/issues/4560 https://github.com/owncloud/android/pull/4562 +* Bugfix - App crashes at start when biometrics fail: [#7134](https://github.com/owncloud/enterprise/issues/7134) + + The crash that happened when biometrics failed due to a system error has been + handled. In this case, an error is shown and pattern or passcode unlock are used + instead of biometrics. + + https://github.com/owncloud/enterprise/issues/7134 + https://github.com/owncloud/android/pull/4564 + # Changelog for ownCloud Android Client [4.5.0] (2025-03-24) The following sections list the changes in ownCloud Android Client 4.5.0 relevant to diff --git a/changelog/unreleased/4564 b/changelog/unreleased/4564 new file mode 100644 index 00000000000..d97ddf6181a --- /dev/null +++ b/changelog/unreleased/4564 @@ -0,0 +1,7 @@ +Bugfix: App crashes at start when biometrics fail + +The crash that happened when biometrics failed due to a system error has been handled. +In this case, an error is shown and pattern or passcode unlock are used instead of biometrics. + +https://github.com/owncloud/enterprise/issues/7134 +https://github.com/owncloud/android/pull/4564 diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/biometric/BiometricActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/biometric/BiometricActivity.kt index 885ddcc58dc..837c2333987 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/biometric/BiometricActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/biometric/BiometricActivity.kt @@ -3,8 +3,9 @@ * * @author David González Verdugo * @author Juan Carlos Garrote Gascón + * @author Jorge Aguado Recio * - * Copyright (C) 2023 ownCloud GmbH. + * Copyright (C) 2025 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -66,7 +67,7 @@ class BiometricActivity : AppCompatActivity() { if (biometricManager.canAuthenticate(BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS) { showBiometricPrompt() } else { - authError() + authError(biometricHasFailed = true) } } @@ -82,13 +83,13 @@ class BiometricActivity : AppCompatActivity() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) Timber.e("onAuthenticationError ($errorCode): $errString") - authError() + authError(biometricHasFailed = errorCode != BiometricPrompt.ERROR_NEGATIVE_BUTTON) } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) if (result.cryptoObject?.cipher != cryptoObject.cipher) { - authError() + authError(biometricHasFailed = true) } else { if (biometricViewModel.shouldAskForNewPassCode()) { biometricViewModel.removePassCode() @@ -110,14 +111,19 @@ class BiometricActivity : AppCompatActivity() { }) // Displays the "log in" prompt. - biometricPrompt.authenticate(promptInfo, cryptoObject) + try { + biometricPrompt.authenticate(promptInfo, cryptoObject) + } catch (e: Exception) { + Timber.e(e, "cryptoObject property has not been initialized correctly") + authError(biometricHasFailed = true) + } } - private fun authError() { + private fun authError(biometricHasFailed: Boolean) { if (PassCodeManager.isPassCodeEnabled()) { - PassCodeManager.onBiometricCancelled(this) + PassCodeManager.onBiometricCancelled(this, biometricHasFailed) } else if (PatternManager.isPatternEnabled()) { - PatternManager.onBiometricCancelled(this) + PatternManager.onBiometricCancelled(this, biometricHasFailed) } finish() diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/biometric/BiometricManager.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/biometric/BiometricManager.kt index 8fbf40c1d59..cdc80e43d01 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/biometric/BiometricManager.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/biometric/BiometricManager.kt @@ -60,11 +60,11 @@ object BiometricManager { activity.startActivity(i) } else if (isPassCodeEnabled()) { // Cancel biometric lock and use passcode unlock method - PassCodeManager.onBiometricCancelled(activity) + PassCodeManager.onBiometricCancelled(activity = activity, biometricHasFailed = false) visibleActivities.add(activity.javaClass) } else if (isPatternEnabled()) { // Cancel biometric lock and use pattern unlock method - PatternManager.onBiometricCancelled(activity) + PatternManager.onBiometricCancelled(activity = activity, biometricHasFailed = false) visibleActivities.add(activity.javaClass) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeActivity.kt index 006b446e1a5..cf89b1d4223 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeActivity.kt @@ -10,9 +10,10 @@ * @author Juan Carlos Garrote Gascón * @author David Crespo Ríos * @author Aitor Ballesteros Pavón + * @author Jorge Aguado Recio * * Copyright (C) 2011 Bartek Przybylski - * Copyright (C) 2024 ownCloud GmbH. + * Copyright (C) 2025 ownCloud GmbH. *
* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -44,6 +45,7 @@ import com.owncloud.android.R import com.owncloud.android.databinding.PasscodelockBinding import com.owncloud.android.domain.utils.Event import com.owncloud.android.extensions.showBiometricDialog +import com.owncloud.android.extensions.showMessageInSnackbar import com.owncloud.android.presentation.documentsprovider.DocumentsProviderUtils.notifyDocumentsProviderRoots import com.owncloud.android.presentation.security.biometric.BiometricStatus import com.owncloud.android.presentation.security.biometric.BiometricViewModel @@ -94,6 +96,10 @@ class PassCodeActivity : AppCompatActivity(), NumberKeyboardListener, EnableBiom setContentView(binding.root) + if (intent.getBooleanExtra(BIOMETRIC_HAS_FAILED, false)) { + showMessageInSnackbar(message = getString(R.string.biometric_not_available)) + } + numberOfPasscodeDigits = passCodeViewModel.getPassCode()?.length ?: passCodeViewModel.getNumberOfPassCodeDigits() passCodeEditTexts = arrayOfNulls(numberOfPasscodeDigits) @@ -474,5 +480,7 @@ class PassCodeActivity : AppCompatActivity(), NumberKeyboardListener, EnableBiom private const val NUM_ATTEMPTS_WITHOUT_TIMER = 3 + const val BIOMETRIC_HAS_FAILED = "BIOMETRIC_HAS_FAILED" + } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeManager.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeManager.kt index 26e9dace002..d165ddc4a1e 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeManager.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeManager.kt @@ -2,8 +2,9 @@ * ownCloud Android client application * * @author Juan Carlos Garrote Gascón + * @author Jorge Aguado Recio * - * Copyright (C) 2023 ownCloud GmbH. + * Copyright (C) 2025 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -52,7 +53,7 @@ object PassCodeManager { return } - askUserForPasscode(activity) + askUserForPasscode(activity = activity, biometricHasFailed = false) } else if (preferencesProvider.getBoolean(PassCodeActivity.PREFERENCE_MIGRATION_REQUIRED, false)) { val intent = Intent(appContext, PassCodeActivity::class.java).apply { action = PassCodeActivity.ACTION_CREATE @@ -86,15 +87,16 @@ object PassCodeManager { fun isPassCodeEnabled(): Boolean = preferencesProvider.getBoolean(PassCodeActivity.PREFERENCE_SET_PASSCODE, false) - private fun askUserForPasscode(activity: Activity) { + private fun askUserForPasscode(activity: Activity, biometricHasFailed: Boolean) { val i = Intent(appContext, PassCodeActivity::class.java).apply { action = PassCodeActivity.ACTION_CHECK flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or Intent.FLAG_ACTIVITY_SINGLE_TOP + putExtra(PassCodeActivity.BIOMETRIC_HAS_FAILED, biometricHasFailed) } activity.startActivity(i) } - fun onBiometricCancelled(activity: Activity) { - askUserForPasscode(activity) + fun onBiometricCancelled(activity: Activity, biometricHasFailed: Boolean) { + askUserForPasscode(activity, biometricHasFailed) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/pattern/PatternActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/pattern/PatternActivity.kt index 8b6b8b9dde6..4afc2bc51dc 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/pattern/PatternActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/pattern/PatternActivity.kt @@ -6,7 +6,9 @@ * @author David González Verdugo * @author Abel García de Prada * @author Juan Carlos Garrote Gascón - * Copyright (C) 2021 ownCloud GmbH. + * @author Jorge Aguado Recio + * + * Copyright (C) 2025 ownCloud GmbH. *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -40,6 +42,7 @@ import com.owncloud.android.R
import com.owncloud.android.data.providers.implementation.OCSharedPreferencesProvider
import com.owncloud.android.databinding.ActivityPatternLockBinding
import com.owncloud.android.extensions.showBiometricDialog
+import com.owncloud.android.extensions.showMessageInSnackbar
import com.owncloud.android.presentation.documentsprovider.DocumentsProviderUtils.notifyDocumentsProviderRoots
import com.owncloud.android.presentation.security.PREFERENCE_LAST_UNLOCK_TIMESTAMP
import com.owncloud.android.presentation.security.biometric.BiometricStatus
@@ -76,6 +79,10 @@ class PatternActivity : AppCompatActivity(), EnableBiometrics {
setContentView(binding.root)
+ if (intent.getBooleanExtra(BIOMETRIC_HAS_FAILED, false)) {
+ showMessageInSnackbar(message = getString(R.string.biometric_not_available))
+ }
+
binding.patternLockView.clearPattern()
// Allow or disallow touches with other visible windows
@@ -354,5 +361,6 @@ class PatternActivity : AppCompatActivity(), EnableBiometrics {
private const val KEY_PATTERN_STRING = "PATTERN_STRING"
private const val PATTERN_HEADER_VIEW_TEXT = "PATTERN_HEADER_VIEW_TEXT"
private const val PATTERN_EXP_VIEW_STATE = "PATTERN_EXP_VIEW_STATE"
+ const val BIOMETRIC_HAS_FAILED = "BIOMETRIC_HAS_FAILED"
}
}
diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/pattern/PatternManager.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/pattern/PatternManager.kt
index 63021b36851..7e75c4cc561 100644
--- a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/pattern/PatternManager.kt
+++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/pattern/PatternManager.kt
@@ -2,8 +2,9 @@
* ownCloud Android client application
*
* @author Juan Carlos Garrote Gascón
+ * @author Jorge Aguado Recio
*
- * Copyright (C) 2023 ownCloud GmbH.
+ * Copyright (C) 2025 ownCloud GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -52,7 +53,7 @@ object PatternManager {
return
}
- askUserForPattern(activity)
+ askUserForPattern(activity = activity, biometricHasFailed = false)
}
visibleActivities.add(activity.javaClass)
@@ -79,15 +80,16 @@ object PatternManager {
fun isPatternEnabled(): Boolean =
preferencesProvider.getBoolean(PatternActivity.PREFERENCE_SET_PATTERN, false)
- private fun askUserForPattern(activity: Activity) {
+ private fun askUserForPattern(activity: Activity, biometricHasFailed: Boolean) {
val i = Intent(appContext, PatternActivity::class.java).apply {
action = PatternActivity.ACTION_CHECK
flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ putExtra(PatternActivity.BIOMETRIC_HAS_FAILED, biometricHasFailed)
}
activity.startActivity(i)
}
- fun onBiometricCancelled(activity: Activity) {
- askUserForPattern(activity)
+ fun onBiometricCancelled(activity: Activity, biometricHasFailed: Boolean) {
+ askUserForPattern(activity, biometricHasFailed)
}
}
diff --git a/owncloudApp/src/main/res/values/strings.xml b/owncloudApp/src/main/res/values/strings.xml
index fb96d1168ef..eccf7c094f7 100644
--- a/owncloudApp/src/main/res/values/strings.xml
+++ b/owncloudApp/src/main/res/values/strings.xml
@@ -321,6 +321,7 @@