diff options
| author | 2024-03-06 19:49:56 +0000 | |
|---|---|---|
| committer | 2024-03-07 14:39:53 +0000 | |
| commit | 95f61def301e010f9577ba113c0995659a7a0dc7 (patch) | |
| tree | 4aa64ea0a66d619b4b5eca436373f5f071285487 | |
| parent | 2966eb208c543cc50c1eab61111410c4306d6b3a (diff) | |
Don't show face messasges if there's a fp message showing (within 3.5s)
Test: atest AlternateBouncerMessageAreaViewModelTest
Fixes: 327252680
Flag: ACONFIG com.android.systemui.device_entry_udfps_refactor STAGING
Change-Id: Ib63346fc3e97922ca0fa5f9e42dc2a6bba58b172
4 files changed, 220 insertions, 6 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt new file mode 100644 index 000000000000..87d1cd571e3c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.hardware.fingerprint.FingerprintManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.shared.model.FaceFailureMessage +import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus +import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage +import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AlternateBouncerMessageAreaViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val fingerprintAuthRepository by lazy { + kosmos.fakeDeviceEntryFingerprintAuthRepository + } + private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository } + private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository } + private val biometricSettingsRepository by lazy { kosmos.fakeBiometricSettingsRepository } + private val underTest: AlternateBouncerMessageAreaViewModel = + kosmos.alternateBouncerMessageAreaViewModel + + @Before + fun setUp() { + biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true) + biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true) + } + + @Test + fun noInitialValue() = + testScope.runTest { + val message by collectLastValue(underTest.message) + bouncerRepository.setAlternateVisible(true) + assertThat(message).isNull() + } + + @Test + fun fingerprintMessage() = + testScope.runTest { + val message by collectLastValue(underTest.message) + bouncerRepository.setAlternateVisible(true) + runCurrent() + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java) + } + + @Test + fun fingerprintLockoutMessage_notShown() = + testScope.runTest { + val message by collectLastValue(underTest.message) + bouncerRepository.setAlternateVisible(true) + runCurrent() + fingerprintAuthRepository.setAuthenticationStatus( + ErrorFingerprintAuthenticationStatus( + msgId = FingerprintManager.FINGERPRINT_ERROR_LOCKOUT, + msg = "test lockout", + ) + ) + assertThat(message).isNull() + } + + @Test + fun alternateBouncerNotVisible_messagesNeverShow() = + testScope.runTest { + val message by collectLastValue(underTest.message) + bouncerRepository.setAlternateVisible(false) + runCurrent() + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + assertThat(message).isNull() + + faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus()) + assertThat(message).isNull() + } + + @Test + fun faceFailMessage() = + testScope.runTest { + val message by collectLastValue(underTest.message) + bouncerRepository.setAlternateVisible(true) + runCurrent() + faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus()) + assertThat(message).isInstanceOf(FaceFailureMessage::class.java) + } + + @Test + fun faceThenFingerprintMessage() = + testScope.runTest { + val message by collectLastValue(underTest.message) + bouncerRepository.setAlternateVisible(true) + runCurrent() + faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus()) + assertThat(message).isInstanceOf(FaceFailureMessage::class.java) + + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java) + } + + @Test + fun fingerprintMessagePreventsFaceMessageFromShowing() = + testScope.runTest { + val message by collectLastValue(underTest.message) + bouncerRepository.setAlternateVisible(true) + runCurrent() + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java) + + faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus()) + assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java) + } + + @Test + fun fingerprintMessageAllowsFaceMessageAfter4000ms() = + testScope.runTest { + val message by collectLastValue(underTest.message) + bouncerRepository.setAlternateVisible(true) + runCurrent() + fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus) + assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java) + + advanceTimeBy(4000) + + faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus()) + assertThat(message).isInstanceOf(FaceFailureMessage::class.java) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt index 9edb4d159d4a..bbe5fedbf12f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt @@ -23,13 +23,19 @@ import com.android.systemui.deviceentry.shared.model.FaceMessage import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage import com.android.systemui.deviceentry.shared.model.FingerprintMessage +import com.android.systemui.statusbar.KeyguardIndicationController.DEFAULT_MESSAGE_TIME +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart /** View model for the alternate bouncer message area. */ @ExperimentalCoroutinesApi @@ -38,19 +44,32 @@ class AlternateBouncerMessageAreaViewModel constructor( biometricMessageInteractor: BiometricMessageInteractor, alternateBouncerInteractor: AlternateBouncerInteractor, + systemClock: com.android.systemui.util.time.SystemClock, ) { - - private val faceMessage: Flow<FaceMessage> = - biometricMessageInteractor.faceMessage.filterNot { it is FaceTimeoutMessage } + private val fingerprintMessageWithTimestamp: Flow<Pair<FingerprintMessage?, Long>> = + biometricMessageInteractor.fingerprintMessage + .filterNot { it is FingerprintLockoutMessage } + .map { Pair(it, systemClock.uptimeMillis()) } + .filterIsInstance<Pair<FingerprintMessage?, Long>>() + .onStart { emit(Pair(null, -3500L)) } private val fingerprintMessage: Flow<FingerprintMessage> = - biometricMessageInteractor.fingerprintMessage.filterNot { it is FingerprintLockoutMessage } + fingerprintMessageWithTimestamp.filter { it.first != null }.map { it.first!! } + private val faceMessage: Flow<FaceMessage> = + biometricMessageInteractor.faceMessage + .filterNot { it is FaceTimeoutMessage } + // Don't show face messages if within the default message time for fp messages to show + .sample(fingerprintMessageWithTimestamp, ::Pair) + .filter { (_, fpMessage) -> + (systemClock.uptimeMillis() - fpMessage.second) >= DEFAULT_MESSAGE_TIME + } + .map { (faceMsg, _) -> faceMsg } val message: Flow<BiometricMessage?> = alternateBouncerInteractor.isVisible.flatMapLatest { isVisible -> if (isVisible) { merge( - faceMessage, fingerprintMessage, + faceMessage, ) } else { flowOf(null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 1ec86aea49d8..c9046217e68a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -147,8 +147,9 @@ public class KeyguardIndicationController { private static final int MSG_SHOW_ACTION_TO_UNLOCK = 1; private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 2; private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300; + public static final long DEFAULT_MESSAGE_TIME = 3500; public static final long DEFAULT_HIDE_DELAY_MS = - 3500 + KeyguardIndicationTextView.Y_IN_DURATION; + DEFAULT_MESSAGE_TIME + KeyguardIndicationTextView.Y_IN_DURATION; private final Context mContext; private final BroadcastDispatcher mBroadcastDispatcher; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt new file mode 100644 index 000000000000..b7d9676040d0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.time.systemClock +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +val Kosmos.alternateBouncerMessageAreaViewModel by + Kosmos.Fixture { + AlternateBouncerMessageAreaViewModel( + biometricMessageInteractor = biometricMessageInteractor, + alternateBouncerInteractor = alternateBouncerInteractor, + systemClock = systemClock, + ) + } |