diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt index dcdcccfd7b4..0afa6134e2e 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt @@ -34,7 +34,6 @@ import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.google.android.setupcompat.template.FooterBarMixin import com.google.android.setupcompat.template.FooterButton import com.google.android.setupdesign.GlifLayout -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch private const val TAG = "FingerprintEnrollFindSensorV2Fragment" @@ -94,12 +93,12 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() { // Set up lottie or animation lifecycleScope.launch { - viewModel.showSfpsLottie.collect { (isFolded, rotation) -> + viewModel.sfpsLottieInfo.collect { (isFolded, rotation) -> setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation)) } } lifecycleScope.launch { - viewModel.showUdfpsLottie.collect { isAccessibilityEnabled -> + viewModel.udfpsLottieInfo.collect { isAccessibilityEnabled -> val lottieAnimation = if (isAccessibilityEnabled) R.raw.udfps_edu_a11y_lottie else R.raw.udfps_edu_lottie setupLottie(view, lottieAnimation) { viewModel.proceedToEnrolling() } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt index 17f8132a7be..90aefc81085 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt @@ -26,13 +26,12 @@ import com.android.systemui.biometrics.shared.model.FingerprintSensorType import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.flow.transform import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -47,41 +46,43 @@ class FingerprintEnrollFindSensorViewModel( ) : ViewModel() { /** Represents the stream of sensor type. */ val sensorType: Flow = - fingerprintEnrollViewModel.sensorType - .filterWhenEducationIsShown() - .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1) + fingerprintEnrollViewModel.sensorType.shareIn( + viewModelScope, + SharingStarted.WhileSubscribed(), + 1 + ) private val _isUdfps: Flow = sensorType.map { it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC } private val _isSfps: Flow = sensorType.map { it == FingerprintSensorType.POWER_BUTTON } - private val _isRearSfps: Flow = - combineTransform(_isSfps, _isUdfps) { v1, v2 -> !v1 && !v2 } + private val _isRearSfps: Flow = sensorType.map { it == FingerprintSensorType.REAR } /** Represents the stream of showing primary button. */ - val showPrimaryButton: Flow = _isUdfps.transform { if (it) emit(true) } + val showPrimaryButton: Flow = _isUdfps.filter { it } - /** Represents the stream of showing sfps lottie, Pair(isFolded, rotation). */ - val showSfpsLottie: Flow> = + private val _showSfpsLottie = _isSfps.filter { it } + /** Represents the stream of showing sfps lottie and the information Pair(isFolded, rotation). */ + val sfpsLottieInfo: Flow> = combineTransform( - _isSfps, + _showSfpsLottie, foldStateViewModel.isFolded, orientationStateViewModel.rotation, - ) { isSfps, isFolded, rotation -> - if (isSfps) emit(Pair(isFolded, rotation)) + ) { _, isFolded, rotation -> + emit(Pair(isFolded, rotation)) } - /** Represents the stream of showing udfps lottie. */ - val showUdfpsLottie: Flow = - combineTransform( - _isUdfps, - accessibilityViewModel.isAccessibilityEnabled, - ) { isUdfps, isAccessibilityEnabled -> - if (isUdfps) emit(isAccessibilityEnabled) + private val _showUdfpsLottie = _isUdfps.filter { it } + /** Represents the stream of showing udfps lottie and whether accessibility is enabled. */ + val udfpsLottieInfo: Flow = + _showUdfpsLottie.combine(accessibilityViewModel.isAccessibilityEnabled) { + _, + isAccessibilityEnabled -> + isAccessibilityEnabled } /** Represents the stream of showing rfps animation. */ - val showRfpsAnimation: Flow = _isRearSfps.transform { if (it) emit(true) } + val showRfpsAnimation: Flow = _isRearSfps.filter { it } private val _showErrorDialog: MutableStateFlow?> = MutableStateFlow(null) /** Represents the stream of showing error dialog. */ @@ -145,16 +146,6 @@ class FingerprintEnrollFindSensorViewModel( navigationViewModel.nextStep() } - // TODO: If we decide to remove previous fragment from activity, then we don't need to check - // whether education is shown for the flows that are subscribed by - // [FingerprintEnrollFindSensorV2Fragment]. - private fun Flow.filterWhenEducationIsShown() = - combineTransform(navigationViewModel.navigationViewModel) { value, navigationViewModel -> - if (navigationViewModel.currStep == Education) { - emit(value) - } - } - class FingerprintEnrollFindSensorViewModelFactory( private val navigationViewModel: FingerprintEnrollNavigationViewModel, private val fingerprintEnrollViewModel: FingerprintEnrollViewModel, diff --git a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt new file mode 100644 index 00000000000..509b0edb22d --- /dev/null +++ b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2023 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.settings.fingerprint2.enrollment.viewmodel + +import android.content.Context +import android.content.res.Configuration +import android.view.accessibility.AccessibilityManager +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.test.core.app.ApplicationProvider +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FoldStateViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.NextStepViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.OrientationStateViewModel +import com.android.settings.testutils2.FakeFingerprintManagerInteractor +import com.android.systemui.biometrics.shared.model.FingerprintSensor +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoJUnitRunner + +/** consistent with [ScreenSizeFoldProvider.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP] */ +private const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600 + +@RunWith(MockitoJUnitRunner::class) +class FingerprintEnrollFindSensorViewModelV2Test { + @JvmField @Rule var rule = MockitoJUnit.rule() + @get:Rule val instantTaskRule = InstantTaskExecutorRule() + + private var backgroundDispatcher = StandardTestDispatcher() + private var testScope = TestScope(backgroundDispatcher) + private lateinit var fakeFingerprintManagerInteractor: FakeFingerprintManagerInteractor + private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel + private lateinit var enrollViewModel: FingerprintEnrollViewModel + private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel + private lateinit var accessibilityViewModel: AccessibilityViewModel + private lateinit var foldStateViewModel: FoldStateViewModel + private lateinit var orientationStateViewModel: OrientationStateViewModel + private lateinit var underTest: FingerprintEnrollFindSensorViewModel + private val context: Context = ApplicationProvider.getApplicationContext() + private val accessibilityManager: AccessibilityManager = + context.getSystemService(AccessibilityManager::class.java)!! + + @Before + fun setup() { + backgroundDispatcher = StandardTestDispatcher() + testScope = TestScope(backgroundDispatcher) + Dispatchers.setMain(backgroundDispatcher) + + fakeFingerprintManagerInteractor = FakeFingerprintManagerInteractor() + gatekeeperViewModel = + FingerprintGatekeeperViewModel.FingerprintGatekeeperViewModelFactory( + null, + fakeFingerprintManagerInteractor + ) + .create(FingerprintGatekeeperViewModel::class.java) + navigationViewModel = + FingerprintEnrollNavigationViewModel.FingerprintEnrollNavigationViewModelFactory( + backgroundDispatcher, + fakeFingerprintManagerInteractor, + gatekeeperViewModel, + canSkipConfirm = true, + ) + .create(FingerprintEnrollNavigationViewModel::class.java) + enrollViewModel = + FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory( + fakeFingerprintManagerInteractor, + backgroundDispatcher + ) + .create(FingerprintEnrollViewModel::class.java) + accessibilityViewModel = + AccessibilityViewModel.AccessibilityViewModelFactory(accessibilityManager) + .create(AccessibilityViewModel::class.java) + foldStateViewModel = + FoldStateViewModel.FoldStateViewModelFactory(context).create(FoldStateViewModel::class.java) + orientationStateViewModel = + OrientationStateViewModel.OrientationViewModelFactory(context) + .create(OrientationStateViewModel::class.java) + underTest = + FingerprintEnrollFindSensorViewModel.FingerprintEnrollFindSensorViewModelFactory( + navigationViewModel, + enrollViewModel, + gatekeeperViewModel, + accessibilityViewModel, + foldStateViewModel, + orientationStateViewModel + ) + .create(FingerprintEnrollFindSensorViewModel::class.java) + + // Navigate to Education page + navigationViewModel.nextStep() + } + @After + fun tearDown() { + Dispatchers.resetMain() + } + + // TODO(b/305094585): test enroll() logic + + @Test + fun currentStepIsEducation() = + testScope.runTest { + var step: NextStepViewModel? = null + val job = launch { + navigationViewModel.navigationViewModel.collectLatest { step = it.currStep } + } + advanceUntilIdle() + assertThat(step).isEqualTo(Education) + job.cancel() + } + + @Test + fun udfpsLottieInfo() = + testScope.runTest { + fakeFingerprintManagerInteractor.sensorProp = + FingerprintSensor( + 0 /* sensorId */, + SensorStrength.STRONG, + 5, + FingerprintSensorType.UDFPS_OPTICAL + ) + + var udfpsLottieInfo: Boolean? = null + val job = launch { underTest.udfpsLottieInfo.collect { udfpsLottieInfo = it } } + + advanceUntilIdle() + assertThat(udfpsLottieInfo).isNotNull() + job.cancel() + } + + @Test + fun sfpsLottieInfoWhenFolded() = + testScope.runTest { + var isFolded = false + var rotation: Int = -1 + val job = launch { + underTest.sfpsLottieInfo.collect { + isFolded = it.first + rotation = it.second + } + } + + val config = createConfiguration(isFolded = true) + foldStateViewModel.onConfigurationChange(config) + advanceUntilIdle() + assertThat(isFolded).isTrue() + assertThat(rotation).isEqualTo(context.display!!.rotation) + job.cancel() + } + + @Test + fun sfpsLottieInfoWhenUnFolded() = + testScope.runTest { + var isFolded = false + var rotation: Int = -1 + val job = launch { + underTest.sfpsLottieInfo.collect { + isFolded = it.first + rotation = it.second + } + } + + val config = createConfiguration(isFolded = false) + foldStateViewModel.onConfigurationChange(config) + advanceUntilIdle() + assertThat(isFolded).isFalse() + assertThat(rotation).isEqualTo(context.display!!.rotation) + job.cancel() + } + + @Test + fun rfpsAnimation() = + testScope.runTest { + fakeFingerprintManagerInteractor.sensorProp = + FingerprintSensor(0 /* sensorId */, SensorStrength.STRONG, 5, FingerprintSensorType.REAR) + + var showRfpsAnimation: Boolean? = null + val job = launch { underTest.showRfpsAnimation.collect { showRfpsAnimation = it } } + + advanceUntilIdle() + assertThat(showRfpsAnimation).isTrue() + job.cancel() + } + + @Test + fun showPrimaryButton_ifUdfps() = + testScope.runTest { + fakeFingerprintManagerInteractor.sensorProp = + FingerprintSensor( + 0 /* sensorId */, + SensorStrength.STRONG, + 5, + FingerprintSensorType.UDFPS_OPTICAL + ) + + var showPrimaryButton: Boolean? = null + val job = launch { underTest.showPrimaryButton.collect { showPrimaryButton = it } } + + advanceUntilIdle() + assertThat(showPrimaryButton).isTrue() + job.cancel() + } + + @Test + fun doesNotShowPrimaryButton_ifNonUdfps() = + testScope.runTest { + var showPrimaryButton: Boolean? = null + val job = launch { underTest.showPrimaryButton.collect { showPrimaryButton = it } } + + advanceUntilIdle() + assertThat(showPrimaryButton).isNull() + job.cancel() + } + + private fun createConfiguration(isFolded: Boolean): Configuration { + val config = Configuration() + config.smallestScreenWidthDp = + if (isFolded) INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1 + else INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1 + return config + } +}