Add tests for Education

Test: atest FingerprintEnrollFindSensorViewModelV2Test
Bug: 295206773

Change-Id: I741ddf49fccae7a301e2fb79194ce8cc6b966070
This commit is contained in:
Hao Dong
2023-08-30 21:09:59 +00:00
parent acd95546b2
commit 03af4571e3
3 changed files with 279 additions and 34 deletions

View File

@@ -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() }

View File

@@ -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<FingerprintSensorType> =
fingerprintEnrollViewModel.sensorType
.filterWhenEducationIsShown()
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
fingerprintEnrollViewModel.sensorType.shareIn(
viewModelScope,
SharingStarted.WhileSubscribed(),
1
)
private val _isUdfps: Flow<Boolean> =
sensorType.map {
it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC
}
private val _isSfps: Flow<Boolean> = sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
private val _isRearSfps: Flow<Boolean> =
combineTransform(_isSfps, _isUdfps) { v1, v2 -> !v1 && !v2 }
private val _isRearSfps: Flow<Boolean> = sensorType.map { it == FingerprintSensorType.REAR }
/** Represents the stream of showing primary button. */
val showPrimaryButton: Flow<Boolean> = _isUdfps.transform { if (it) emit(true) }
val showPrimaryButton: Flow<Boolean> = _isUdfps.filter { it }
/** Represents the stream of showing sfps lottie, Pair(isFolded, rotation). */
val showSfpsLottie: Flow<Pair<Boolean, Int>> =
private val _showSfpsLottie = _isSfps.filter { it }
/** Represents the stream of showing sfps lottie and the information Pair(isFolded, rotation). */
val sfpsLottieInfo: Flow<Pair<Boolean, Int>> =
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<Boolean> =
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<Boolean> =
_showUdfpsLottie.combine(accessibilityViewModel.isAccessibilityEnabled) {
_,
isAccessibilityEnabled ->
isAccessibilityEnabled
}
/** Represents the stream of showing rfps animation. */
val showRfpsAnimation: Flow<Boolean> = _isRearSfps.transform { if (it) emit(true) }
val showRfpsAnimation: Flow<Boolean> = _isRearSfps.filter { it }
private val _showErrorDialog: MutableStateFlow<Pair<Int, Boolean>?> = 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 <T> Flow<T>.filterWhenEducationIsShown() =
combineTransform(navigationViewModel.navigationViewModel) { value, navigationViewModel ->
if (navigationViewModel.currStep == Education) {
emit(value)
}
}
class FingerprintEnrollFindSensorViewModelFactory(
private val navigationViewModel: FingerprintEnrollNavigationViewModel,
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,

View File

@@ -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
}
}