Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 95f61def authored by Beverly's avatar Beverly
Browse files

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
parent 2966eb20
Loading
Loading
Loading
Loading
+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)
        }
}
+24 −5
Original line number Diff line number Diff line
@@ -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)
+2 −1
Original line number Diff line number Diff line
@@ -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;
+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,
        )
    }