diff --git a/changelog/unreleased/4967 b/changelog/unreleased/4967 new file mode 100644 index 00000000000..13df984c35c --- /dev/null +++ b/changelog/unreleased/4967 @@ -0,0 +1,6 @@ +Bugfix: Hide keyboard and clear error when brute-force countdown ends + +The number keyboard has been hidden during the brute-force protection countdown so users cannot interact with it. The wrong-passcode error message has been cleared once the countdown ends, leaving a clean screen for the next attempt. + +https://github.com/owncloud/android/issues/4874 +https://github.com/owncloud/android/pull/4967 diff --git a/owncloudApp/src/androidTest/java/com/owncloud/android/settings/security/PassCodeActivityTest.kt b/owncloudApp/src/androidTest/java/com/owncloud/android/settings/security/PassCodeActivityTest.kt index acca5a6868d..ded6a1bd7e2 100644 --- a/owncloudApp/src/androidTest/java/com/owncloud/android/settings/security/PassCodeActivityTest.kt +++ b/owncloudApp/src/androidTest/java/com/owncloud/android/settings/security/PassCodeActivityTest.kt @@ -66,7 +66,7 @@ class PassCodeActivityTest { private lateinit var timeToUnlockLiveData: MutableLiveData> private lateinit var finishTimeToUnlockLiveData: MutableLiveData> - private lateinit var statusLiveData: MutableLiveData + private lateinit var statusLiveData: MutableLiveData> private lateinit var passcodeLiveData: MutableLiveData private lateinit var passCodeViewModel: PassCodeViewModel @@ -206,7 +206,7 @@ class PassCodeActivityTest { // Open Activity in passcode creation mode openPasscodeActivity(PassCodeActivity.ACTION_CREATE) - statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.NO_CONFIRM)) + statusLiveData.postValue(Event(Status(PasscodeAction.CREATE, PasscodeType.NO_CONFIRM))) with(R.id.header) { isDisplayed(true) @@ -224,7 +224,7 @@ class PassCodeActivityTest { // Open Activity in passcode creation mode openPasscodeActivity(PassCodeActivity.ACTION_CREATE) - statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM)) + statusLiveData.postValue(Event(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM))) // Click dialog's enable option onView(withText(R.string.common_yes)).perform(click()) @@ -238,7 +238,7 @@ class PassCodeActivityTest { // Open Activity in passcode creation mode openPasscodeActivity(PassCodeActivity.ACTION_CREATE) - statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.ERROR)) + statusLiveData.postValue(Event(Status(PasscodeAction.CREATE, PasscodeType.ERROR))) with(R.id.header) { isDisplayed(true) @@ -278,7 +278,7 @@ class PassCodeActivityTest { // Open Activity in passcode deletion mode openPasscodeActivity(PassCodeActivity.ACTION_REMOVE) - statusLiveData.postValue(Status(PasscodeAction.REMOVE, PasscodeType.OK)) + statusLiveData.postValue(Event(Status(PasscodeAction.REMOVE, PasscodeType.OK))) assertEquals(activityScenario.result.resultCode, Activity.RESULT_OK) } @@ -288,7 +288,7 @@ class PassCodeActivityTest { // Open Activity in passcode deletion mode openPasscodeActivity(PassCodeActivity.ACTION_REMOVE) - statusLiveData.postValue(Status(PasscodeAction.REMOVE, PasscodeType.ERROR)) + statusLiveData.postValue(Event(Status(PasscodeAction.REMOVE, PasscodeType.ERROR))) with(R.id.header) { isDisplayed(true) @@ -311,7 +311,7 @@ class PassCodeActivityTest { // Open Activity in passcode creation mode openPasscodeActivity(PassCodeActivity.ACTION_CREATE) - statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM)) + statusLiveData.postValue(Event(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM))) onView(withText(R.string.biometric_dialog_title)).check(matches(isDisplayed())) onView(withText(R.string.common_yes)).check(matches(isDisplayed())) @@ -325,7 +325,7 @@ class PassCodeActivityTest { // Open Activity in passcode creation mode openPasscodeActivity(PassCodeActivity.ACTION_CREATE) - statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM)) + statusLiveData.postValue(Event(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM))) onView(withText(R.string.common_yes)).perform(click()) @@ -340,7 +340,7 @@ class PassCodeActivityTest { // Open Activity in passcode creation mode openPasscodeActivity(PassCodeActivity.ACTION_CREATE) - statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM)) + statusLiveData.postValue(Event(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM))) onView(withText(R.string.common_no)).perform(click()) 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 cb3157e39b1..47de950a284 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 @@ -228,13 +228,15 @@ class PassCodeActivity : ToolbarActivity(), NumberKeyboardListener, EnableBiomet }) passCodeViewModel.getFinishedTimeToUnlockLiveData.observe(this, Event.EventObserver { binding.lockTime.visibility = View.INVISIBLE + binding.numberKeyboard.visibility = View.VISIBLE + binding.passcodeError.visibility = View.INVISIBLE for (editText: EditText? in passCodeEditTexts) { editText?.isEnabled = true } passCodeEditTexts.first()?.requestFocus() }) - passCodeViewModel.status.observe(this) { status -> + passCodeViewModel.status.observe(this, Event.EventObserver { status -> when (status.action) { PasscodeAction.CHECK -> { when (status.type) { @@ -260,7 +262,7 @@ class PassCodeActivity : ToolbarActivity(), NumberKeyboardListener, EnableBiomet } } } - } + }) passCodeViewModel.passcode.observe(this) { passcode -> passCodeEditTexts.forEachIndexed { index, editText -> @@ -346,7 +348,10 @@ class PassCodeActivity : ToolbarActivity(), NumberKeyboardListener, EnableBiomet private fun lockScreen() { val timeToUnlock = passCodeViewModel.getTimeToUnlockLeft() if (timeToUnlock > 0) { + binding.passcodeError.setText(R.string.pass_code_wrong) + binding.passcodeError.visibility = View.VISIBLE binding.lockTime.visibility = View.VISIBLE + binding.numberKeyboard.visibility = View.INVISIBLE for (editText: EditText? in passCodeEditTexts) { editText?.isEnabled = false } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeViewModel.kt index 0e5e46b6a4d..97ea1603527 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/security/passcode/PassCodeViewModel.kt @@ -57,8 +57,8 @@ class PassCodeViewModel( val passcode: LiveData get() = _passcode - private var _status = MutableLiveData() - val status: LiveData + private var _status = MutableLiveData>() + val status: LiveData> get() = _status private var numberOfPasscodeDigits: Int @@ -116,24 +116,24 @@ class PassCodeViewModel( if (passCode != null && passCode.length < getNumberOfPassCodeDigits()) { setMigrationRequired(true) removePassCode() - _status.postValue(Status(PasscodeAction.CHECK, PasscodeType.MIGRATION)) + _status.postValue(Event(Status(PasscodeAction.CHECK, PasscodeType.MIGRATION))) } - _status.postValue(Status(PasscodeAction.CHECK, PasscodeType.OK)) + _status.postValue(Event(Status(PasscodeAction.CHECK, PasscodeType.OK))) resetNumberOfAttempts() } else { increaseNumberOfAttempts() clearPassCode() - _status.postValue(Status(PasscodeAction.CHECK, PasscodeType.ERROR)) + _status.postValue(Event(Status(PasscodeAction.CHECK, PasscodeType.ERROR))) } } private fun actionRemovePasscode() { if (checkPassCodeIsValid(passcodeString.toString())) { removePassCode() - _status.postValue(Status(PasscodeAction.REMOVE, PasscodeType.OK)) + _status.postValue(Event(Status(PasscodeAction.REMOVE, PasscodeType.OK))) } else { clearPassCode() - _status.postValue(Status(PasscodeAction.REMOVE, PasscodeType.ERROR)) + _status.postValue(Event(Status(PasscodeAction.REMOVE, PasscodeType.ERROR))) } } @@ -142,13 +142,13 @@ class PassCodeViewModel( if (!confirmingPassCode) { requestPassCodeConfirmation() clearPassCode() - _status.postValue(Status(PasscodeAction.CREATE, PasscodeType.NO_CONFIRM)) + _status.postValue(Event(Status(PasscodeAction.CREATE, PasscodeType.NO_CONFIRM))) } else if (confirmPassCode()) { setPassCode() - _status.postValue(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM)) + _status.postValue(Event(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM))) } else { clearPassCode() - _status.postValue(Status(PasscodeAction.CREATE, PasscodeType.ERROR)) + _status.postValue(Event(Status(PasscodeAction.CREATE, PasscodeType.ERROR))) } } diff --git a/owncloudApp/src/test/java/com/owncloud/android/presentation/viewmodels/security/PassCodeViewModelTest.kt b/owncloudApp/src/test/java/com/owncloud/android/presentation/viewmodels/security/PassCodeViewModelTest.kt index 4e3f982b3eb..e7654a98944 100644 --- a/owncloudApp/src/test/java/com/owncloud/android/presentation/viewmodels/security/PassCodeViewModelTest.kt +++ b/owncloudApp/src/test/java/com/owncloud/android/presentation/viewmodels/security/PassCodeViewModelTest.kt @@ -143,7 +143,7 @@ class PassCodeViewModelTest : ViewModelTest() { passCodeViewModel.onNumberClicked(1) passCodeViewModel.onNumberClicked(1) - assertEquals(Status(PasscodeAction.CHECK, PasscodeType.OK), passCodeViewModel.status.value) + assertEquals(Status(PasscodeAction.CHECK, PasscodeType.OK), passCodeViewModel.status.value?.peekContent()) verify(exactly = 1) { preferencesProvider.putInt(PREFERENCE_LOCK_ATTEMPTS, 0) @@ -161,7 +161,7 @@ class PassCodeViewModelTest : ViewModelTest() { passCodeViewModel.onNumberClicked(1) passCodeViewModel.onNumberClicked(1) - assertEquals(Status(PasscodeAction.CHECK, PasscodeType.ERROR), passCodeViewModel.status.value) + assertEquals(Status(PasscodeAction.CHECK, PasscodeType.ERROR), passCodeViewModel.status.value?.peekContent()) verify(exactly = 1) { preferencesProvider.putInt(PREFERENCE_LOCK_ATTEMPTS, any()) @@ -180,7 +180,7 @@ class PassCodeViewModelTest : ViewModelTest() { passCodeViewModel.onNumberClicked(1) passCodeViewModel.onNumberClicked(1) - assertEquals(Status(PasscodeAction.REMOVE, PasscodeType.OK), passCodeViewModel.status.value) + assertEquals(Status(PasscodeAction.REMOVE, PasscodeType.OK), passCodeViewModel.status.value?.peekContent()) verify(exactly = 1) { preferencesProvider.removePreference(PREFERENCE_PASSCODE) @@ -199,7 +199,7 @@ class PassCodeViewModelTest : ViewModelTest() { passCodeViewModel.onNumberClicked(1) passCodeViewModel.onNumberClicked(1) - assertEquals(Status(PasscodeAction.REMOVE, PasscodeType.ERROR), passCodeViewModel.status.value) + assertEquals(Status(PasscodeAction.REMOVE, PasscodeType.ERROR), passCodeViewModel.status.value?.peekContent()) } @Test @@ -212,7 +212,7 @@ class PassCodeViewModelTest : ViewModelTest() { passCodeViewModel.onNumberClicked(1) passCodeViewModel.onNumberClicked(1) - assertEquals(Status(PasscodeAction.CREATE, PasscodeType.NO_CONFIRM), passCodeViewModel.status.value) + assertEquals(Status(PasscodeAction.CREATE, PasscodeType.NO_CONFIRM), passCodeViewModel.status.value?.peekContent()) } @Test @@ -231,7 +231,7 @@ class PassCodeViewModelTest : ViewModelTest() { passCodeViewModel.onNumberClicked(1) passCodeViewModel.onNumberClicked(1) - assertEquals(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM), passCodeViewModel.status.value) + assertEquals(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM), passCodeViewModel.status.value?.peekContent()) verify(exactly = 1) { preferencesProvider.putString(PREFERENCE_PASSCODE, any()) @@ -255,7 +255,7 @@ class PassCodeViewModelTest : ViewModelTest() { passCodeViewModel.onNumberClicked(1) passCodeViewModel.onNumberClicked(1) - assertEquals(Status(PasscodeAction.CREATE, PasscodeType.ERROR), passCodeViewModel.status.value) + assertEquals(Status(PasscodeAction.CREATE, PasscodeType.ERROR), passCodeViewModel.status.value?.peekContent()) } @Test