Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt 0 → 100644 +161 −0 Original line number Diff line number Diff line /* * 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) } } packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt +24 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt 0 → 100644 +33 −0 Original line number Diff line number Diff line /* * 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, ) } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt 0 → 100644 +161 −0 Original line number Diff line number Diff line /* * 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) } }
packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt +24 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading
packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading
packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt 0 → 100644 +33 −0 Original line number Diff line number Diff line /* * 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, ) }