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

Commit 694e6fd6 authored by Grace Cheng's avatar Grace Cheng
Browse files

SL composable/viewmodel cleanup

Moves comment to public variable, updates visibility modifiers for scope,
adds test for secure lock device viewmodel, removes applicationscope from
viewmodel, change mutable state flows to snapshot state where possible for
performance

Flag: android.security.secure_lock_device
Bug: 401645997
Test: atest PromptViewModelTest
Test: atest SecureLockDeviceBiometricAuthContentViewModelTest
Test: atest BiometricAuthIconViewModelTest
Change-Id: I2e55a66872d6be797b194853a1d9a160fa75619c
parent a4bde1d7
Loading
Loading
Loading
Loading
+121 −0
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR
import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.platform.test.annotations.EnableFlags
import android.security.Flags.FLAG_SECURE_LOCK_DEVICE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -41,9 +43,11 @@ import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.biometrics.ui.viewmodel.BiometricAuthIconViewModel.BiometricAuthModalities
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.securelockdevice.ui.viewmodel.SecureLockDeviceBiometricAuthContentViewModel
import com.android.systemui.securelockdevice.ui.viewmodel.secureLockDeviceBiometricAuthContentViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runCurrent
@@ -77,6 +81,7 @@ class BiometricAuthIconViewModelTest() : SysuiTestCase() {
        } else if (sensorType == TYPE_REAR) {
            kosmos.fingerprintPropertyRepository.supportsRearFps(sensorStrength)
        }
        kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
    }

    private fun enrollFace(isStrongBiometric: Boolean) {
@@ -86,6 +91,7 @@ class BiometricAuthIconViewModelTest() : SysuiTestCase() {
                strength = if (isStrongBiometric) SensorStrength.STRONG else SensorStrength.WEAK,
            )
        kosmos.facePropertyRepository.setSensorInfo(faceSensorInfo)
        kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
    }

    private fun startBiometricPrompt(hasFpAuth: Boolean, isImplicitFlow: Boolean = false) {
@@ -103,12 +109,22 @@ class BiometricAuthIconViewModelTest() : SysuiTestCase() {
        }
    }

    private fun startSecureLockDevicePrompt() {
        secureLockDeviceViewModel!!.showAuthenticating()
    }

    private fun initPromptViewModel() {
        promptViewModel = kosmos.promptViewModel
        underTest = promptViewModel!!.iconViewModel.internal
        underTest.activateIn(testScope)
    }

    private fun initSecureLockDeviceViewModel() {
        secureLockDeviceViewModel = kosmos.secureLockDeviceBiometricAuthContentViewModel
        underTest = secureLockDeviceViewModel!!.iconViewModel
        underTest.activateIn(testScope)
    }

    @Test
    fun activeBiometricAuthType_basedOnModalitiesAndFaceMode_forBiometricPrompt_face() {
        testScope.runTest {
@@ -127,6 +143,21 @@ class BiometricAuthIconViewModelTest() : SysuiTestCase() {
        }
    }

    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @Test
    fun activeBiometricAuthType_basedOnModalities_forSecureLockDevice_face() {
        testScope.runTest {
            initSecureLockDeviceViewModel()
            val activeBiometricAuthType by collectLastValue(underTest.activeBiometricAuthType)

            enrollFace(isStrongBiometric = true)
            runCurrent()
            startSecureLockDevicePrompt()

            assertThat(activeBiometricAuthType).isEqualTo(BiometricAuthModalities.Face)
        }
    }

    @Test
    fun activeBiometricAuthType_basedOnModalitiesAndFaceMode_forBiometricPrompt_sfps() {
        testScope.runTest {
@@ -147,6 +178,24 @@ class BiometricAuthIconViewModelTest() : SysuiTestCase() {
        }
    }

    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @Test
    fun activeBiometricAuthType_basedOnModalities_forSecureLockDevice_sfps() {
        testScope.runTest {
            initSecureLockDeviceViewModel()
            val activeBiometricAuthType by collectLastValue(underTest.activeBiometricAuthType)

            enrollFingerprint(
                sensorStrength = SensorStrength.STRONG,
                sensorType = TYPE_POWER_BUTTON,
            )
            runCurrent()
            startSecureLockDevicePrompt()

            assertThat(activeBiometricAuthType).isEqualTo(BiometricAuthModalities.Sfps)
        }
    }

    @Test
    fun activeBiometricAuthType_basedOnModalitiesAndFaceMode_forBiometricPrompt_nonSfps() {
        testScope.runTest {
@@ -168,6 +217,24 @@ class BiometricAuthIconViewModelTest() : SysuiTestCase() {
        }
    }

    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @Test
    fun activeBiometricAuthType_basedOnModalities_forSecureLockDevice_nonSfps() {
        testScope.runTest {
            initSecureLockDeviceViewModel()
            val activeBiometricAuthType by collectLastValue(underTest.activeBiometricAuthType)

            enrollFingerprint(
                sensorStrength = SensorStrength.STRONG,
                sensorType = TYPE_UDFPS_OPTICAL,
            )
            runCurrent()
            startSecureLockDevicePrompt()

            assertThat(activeBiometricAuthType).isEqualTo(BiometricAuthModalities.NonSfps)
        }
    }

    @Test
    fun activeBiometricAuthType_basedOnModalitiesAndFaceMode_forBiometricPrompt_sfpsCoexImplicit() {
        testScope.runTest {
@@ -212,6 +279,25 @@ class BiometricAuthIconViewModelTest() : SysuiTestCase() {
        }
    }

    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @Test
    fun activeBiometricAuthType_basedOnModalities_forSecureLockDevice_sfpsCoex() {
        testScope.runTest {
            initSecureLockDeviceViewModel()
            val activeBiometricAuthType by collectLastValue(underTest.activeBiometricAuthType)

            enrollFingerprint(
                sensorStrength = SensorStrength.STRONG,
                sensorType = TYPE_POWER_BUTTON,
            )
            enrollFace(isStrongBiometric = true)
            runCurrent()
            startSecureLockDevicePrompt()

            assertThat(activeBiometricAuthType).isEqualTo(BiometricAuthModalities.SfpsCoex)
        }
    }

    @Test
    fun activeBiometricAuthType_basedOnModalitiesAndFaceMode_forBiometricPrompt_coexNonSfpsImplicit() {
        testScope.runTest {
@@ -256,6 +342,41 @@ class BiometricAuthIconViewModelTest() : SysuiTestCase() {
        }
    }

    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @Test
    fun activeBiometricAuthType_basedOnModalities_forSecureLockDevice_coexNonSfps() {
        testScope.runTest {
            initSecureLockDeviceViewModel()
            val activeBiometricAuthType by collectLastValue(underTest.activeBiometricAuthType)

            enrollFingerprint(
                sensorStrength = SensorStrength.STRONG,
                sensorType = TYPE_UDFPS_OPTICAL,
            )
            enrollFace(isStrongBiometric = true)
            runCurrent()
            startSecureLockDevicePrompt()

            assertThat(activeBiometricAuthType).isEqualTo(BiometricAuthModalities.NonSfpsCoex)
        }
    }

    @EnableFlags(FLAG_SECURE_LOCK_DEVICE)
    @Test
    fun activeBiometricAuthNone_forNonStrongBiometrics_forSecureLockDevice() {
        testScope.runTest {
            initSecureLockDeviceViewModel()
            val activeBiometricAuthType by collectLastValue(underTest.activeBiometricAuthType)

            enrollFingerprint(sensorStrength = SensorStrength.WEAK, sensorType = TYPE_UDFPS_OPTICAL)
            enrollFace(isStrongBiometric = false)
            runCurrent()
            startSecureLockDevicePrompt()

            assertThat(activeBiometricAuthType).isEqualTo(BiometricAuthModalities.None)
        }
    }

    /** Initialize the prompt according to the test configuration. */
    private fun PromptSelectorInteractor.initializePrompt(
        fingerprint: FingerprintSensorPropertiesInternal? = null,
+78 −4
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.systemui.securelockdevice.domain.interactor.secureLockDeviceI
import com.android.systemui.testKosmos
import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -50,21 +51,32 @@ class SecureLockDeviceBiometricAuthContentViewModelTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val testScope = kosmos.testScope
    private lateinit var secureLockDeviceInteractor: SecureLockDeviceInteractor
    private lateinit var underTest: SecureLockDeviceBiometricAuthContentViewModel

    @Before
    fun setUp() {
        kosmos.fakeSecureLockDeviceRepository.onSecureLockDeviceEnabled()
        kosmos.fakeSecureLockDeviceRepository.onSuccessfulPrimaryAuth()
        secureLockDeviceInteractor = kosmos.secureLockDeviceInteractor
    }

    @Test
    fun onBiometricAuthRequested_showsAuthenticating_() =
        testScope.runTest {
            val underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
            underTest.activateIn(this)
            val isAuthenticating by collectLastValue(underTest.isAuthenticating)

            secureLockDeviceInteractor.onBiometricAuthRequested()
            runCurrent()

        underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
        underTest.activateIn(testScope)
            assertThat(isAuthenticating).isTrue()
        }

    @Test
    fun updatesStateAndPlaysHaptics_onFaceFailureOrError() =
        testScope.runTest {
            val underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
            underTest.activateIn(this)
            val isAuthenticating by collectValues(underTest.isAuthenticating)
            val isAuthenticated by collectLastValue(underTest.isAuthenticated)
            val showingError by collectValues(underTest.showingError)
@@ -90,6 +102,8 @@ class SecureLockDeviceBiometricAuthContentViewModelTest : SysuiTestCase() {
    @Test
    fun updatesStateOnRetryAfterFaceFailureOrError() =
        testScope.runTest {
            val underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
            underTest.activateIn(this)
            val isAuthenticating by collectLastValue(underTest.isAuthenticating)
            val isAuthenticated by collectLastValue(underTest.isAuthenticated)
            val showingError by collectLastValue(underTest.showingError)
@@ -115,6 +129,8 @@ class SecureLockDeviceBiometricAuthContentViewModelTest : SysuiTestCase() {
    @Test
    fun updatesStateAndSkipsHaptics_onFaceHelp() =
        testScope.runTest {
            val underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
            underTest.activateIn(this)
            val isAuthenticating by collectLastValue(underTest.isAuthenticating)
            val isAuthenticated by collectLastValue(underTest.isAuthenticated)
            val showingError by collectLastValue(underTest.showingError)
@@ -133,6 +149,8 @@ class SecureLockDeviceBiometricAuthContentViewModelTest : SysuiTestCase() {
    @Test
    fun updatesState_onFaceSuccess_andPlaysHapticsOnConfirm() =
        testScope.runTest {
            val underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
            underTest.activateIn(this)
            val isAuthenticating by collectLastValue(underTest.isAuthenticating)
            val isAuthenticated by collectLastValue(underTest.isAuthenticated)
            val showingError by collectLastValue(underTest.showingError)
@@ -161,6 +179,8 @@ class SecureLockDeviceBiometricAuthContentViewModelTest : SysuiTestCase() {
    @Test
    fun updatesStateAndPlaysHaptics_onFingerprintFailureOrError() =
        testScope.runTest {
            val underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
            underTest.activateIn(this)
            val isAuthenticating by collectValues(underTest.isAuthenticating)
            val isAuthenticated by collectLastValue(underTest.isAuthenticated)
            val showingError by collectValues(underTest.showingError)
@@ -186,6 +206,8 @@ class SecureLockDeviceBiometricAuthContentViewModelTest : SysuiTestCase() {
    @Test
    fun updatesStateAndSkipsHaptics_onFingerprintHelp() =
        testScope.runTest {
            val underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
            underTest.activateIn(this)
            val isAuthenticating by collectLastValue(underTest.isAuthenticating)
            val isAuthenticated by collectLastValue(underTest.isAuthenticated)
            val showingError by collectLastValue(underTest.showingError)
@@ -204,6 +226,8 @@ class SecureLockDeviceBiometricAuthContentViewModelTest : SysuiTestCase() {
    @Test
    fun updatesStateAndPlaysHaptics_onFingerprintSuccess() =
        testScope.runTest {
            val underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
            underTest.activateIn(this)
            val isAuthenticating by collectLastValue(underTest.isAuthenticating)
            val isAuthenticated by collectLastValue(underTest.isAuthenticated)
            val showingError by collectLastValue(underTest.showingError)
@@ -219,4 +243,54 @@ class SecureLockDeviceBiometricAuthContentViewModelTest : SysuiTestCase() {
            assertThat(isAuthenticated?.isAuthenticatedAndConfirmed).isTrue()
            assertThat(kosmos.fakeMSDLPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK)
        }

    @Test
    fun onSuccessfulAuthentication_notifiesInteractorAndHides_uponAnimationFinished() =
        testScope.runTest {
            val underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
            underTest.activateIn(this)
            val isReadyToDismiss by
                collectLastValue(secureLockDeviceInteractor.isFullyUnlockedAndReadyToDismiss)

            underTest.startAppearAnimation()
            runCurrent()

            assertThat(underTest.isVisible).isTrue()
            assertThat(isReadyToDismiss).isFalse()

            underTest.showAuthenticated(BiometricModality.Fingerprint)
            runCurrent()

            assertThat(underTest.isAuthenticationComplete).isTrue()
            assertThat(isReadyToDismiss).isFalse()

            underTest.onIconAnimationFinished()
            runCurrent()

            assertThat(isReadyToDismiss).isTrue()
            assertThat(underTest.isVisible).isFalse()
        }

    @Test
    fun onDeactivated_hidesComposable_notifiesInteractor() =
        testScope.runTest {
            val underTest = kosmos.secureLockDeviceBiometricAuthContentViewModel
            val job = Job()
            underTest.activateIn(this, job)
            runCurrent()

            secureLockDeviceInteractor.onBiometricAuthRequested()
            underTest.startAppearAnimation()
            runCurrent()

            assertThat(underTest.isVisible).isTrue()
            assertThat(secureLockDeviceInteractor.isBiometricAuthVisible.value).isTrue()

            job.cancel()
            runCurrent()

            // THEN the viewmodel is no longer visible and the interactor is notified
            assertThat(underTest.isVisible).isFalse()
            assertThat(secureLockDeviceInteractor.isBiometricAuthVisible.value).isFalse()
        }
}
+1 −1
Original line number Diff line number Diff line
@@ -162,11 +162,11 @@ constructor(
            ?: secureLockDeviceViewModel?.isAuthenticated
            ?: emptyFlow()

    /** If the auth is pending confirmation. */
    private val isPendingConfirmation: Flow<Boolean> =
        isAuthenticated.map { authState ->
            authState.isAuthenticated && authState.needsUserConfirmation
        }
    /** If the auth is pending confirmation. */
    val isPendingConfirmationState: Boolean by
        isPendingConfirmation.hydratedStateOf(
            traceName = "isPendingConfirmation",
+2 −2
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ import com.android.systemui.bouncer.shared.model.SecureLockDeviceBouncerActionBu
import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
import com.android.systemui.lifecycle.rememberActivated
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
import com.android.systemui.securelockdevice.ui.viewmodel.SecureLockDeviceBiometricAuthContentViewModel
import com.android.systemui.util.ui.compose.LottieColorUtils
@@ -88,7 +88,7 @@ fun SecureLockDeviceContent(
    modifier: Modifier = Modifier,
) {
    val secureLockDeviceViewModel =
        rememberActivated(traceName = "SecureLockDeviceBiometricAuthContentViewModel") {
        rememberViewModel(traceName = "SecureLockDeviceBiometricAuthContentViewModel") {
            secureLockDeviceViewModelFactory.create()
        }

+50 −53

File changed.

Preview size limit exceeded, changes collapsed.

Loading