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 @@ Biometric log in Log in using your biometric credential. Do you want to additionally activate biometric security? + Biometric unlock is not available because system could not provide %1$s music player %1$s (playing)