1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel 17 18 import android.hardware.fingerprint.FingerprintEnrollOptions 19 import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY 20 import androidx.lifecycle.ViewModel 21 import androidx.lifecycle.ViewModelProvider 22 import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY 23 import androidx.lifecycle.viewModelScope 24 import androidx.lifecycle.viewmodel.initializer 25 import androidx.lifecycle.viewmodel.viewModelFactory 26 import com.android.settings.SettingsApplication 27 import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor 28 import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason 29 import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState 30 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education 31 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Enrollment 32 import com.android.systemui.biometrics.shared.model.FingerprintSensorType 33 import kotlinx.coroutines.flow.Flow 34 import kotlinx.coroutines.flow.MutableStateFlow 35 import kotlinx.coroutines.flow.SharingStarted 36 import kotlinx.coroutines.flow.combine 37 import kotlinx.coroutines.flow.filterNotNull 38 import kotlinx.coroutines.flow.map 39 import kotlinx.coroutines.flow.shareIn 40 import kotlinx.coroutines.flow.transformLatest 41 import kotlinx.coroutines.flow.update 42 43 /** Represents all of the fingerprint information needed for a fingerprint enrollment process. */ 44 class FingerprintEnrollViewModel( 45 private val fingerprintManagerInteractor: FingerprintManagerInteractor, 46 gatekeeperViewModel: FingerprintGatekeeperViewModel, 47 val navigationViewModel: FingerprintNavigationViewModel, 48 ) : ViewModel() { 49 50 /** 51 * Cached value of [FingerprintSensorType] 52 * 53 * This is typically used by fragments that change their layout/behavior based on this 54 * information. This value should be set before any fragment is created. 55 */ 56 var sensorTypeCached: FingerprintSensorType? = null 57 private var _enrollReason: Flow<EnrollReason?> = 58 navigationViewModel.currentScreen.map { 59 when (it) { 60 is Enrollment -> EnrollReason.EnrollEnrolling 61 is Education -> EnrollReason.FindSensor 62 else -> null 63 } 64 } 65 66 private val enrollOptions: MutableStateFlow<FingerprintEnrollOptions?> = MutableStateFlow(null) 67 68 /** Represents the stream of [FingerprintSensorType] */ 69 val sensorType: Flow<FingerprintSensorType?> = 70 fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType } 71 72 /** 73 * A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for 74 * an enrollment process 75 * 76 * This flow should be the only flow which calls enroll(). 77 */ 78 val _enrollFlow: Flow<FingerEnrollState> = 79 combine(gatekeeperViewModel.gatekeeperInfo, _enrollReason, enrollOptions) { 80 hardwareAuthToken, 81 enrollReason, 82 enrollOptions -> 83 Triple(hardwareAuthToken, enrollReason, enrollOptions) 84 } 85 .transformLatest { 86 /** [transformLatest] is used as we want to make sure to cancel previous API call. */ 87 (hardwareAuthToken, enrollReason, enrollOptions) -> 88 if ( 89 hardwareAuthToken is GatekeeperInfo.GatekeeperPasswordInfo && 90 enrollReason != null && 91 enrollOptions != null 92 ) { 93 fingerprintManagerInteractor 94 .enroll(hardwareAuthToken.token, enrollReason, enrollOptions) 95 .collect { emit(it) } 96 } 97 } 98 .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 0) 99 100 /** 101 * This flow will kick off education when 102 * 1) There is an active subscriber to this flow 103 * 2) shouldEnroll is true and we are on the FindSensor step 104 */ 105 val educationEnrollFlow: Flow<FingerEnrollState?> = 106 _enrollReason.filterNotNull().transformLatest { enrollReason -> 107 if (enrollReason == EnrollReason.FindSensor) { 108 _enrollFlow.collect { event -> emit(event) } 109 } else { 110 emit(null) 111 } 112 } 113 114 /** 115 * This flow will kick off enrollment when 116 * 1) There is an active subscriber to this flow 117 * 2) shouldEnroll is true and we are on the EnrollEnrolling step 118 */ 119 val enrollFlow: Flow<FingerEnrollState?> = 120 _enrollReason.filterNotNull().transformLatest { enrollReason -> 121 if (enrollReason == EnrollReason.EnrollEnrolling) { 122 _enrollFlow.collect { event -> emit(event) } 123 } else { 124 emit(null) 125 } 126 } 127 128 /** Starts enrollment. */ 129 fun enroll(options: FingerprintEnrollOptions) { 130 enrollOptions.update { options } 131 } 132 133 companion object { 134 val Factory: ViewModelProvider.Factory = viewModelFactory { 135 initializer { 136 val settingsApplication = this[APPLICATION_KEY] as SettingsApplication 137 val biometricEnvironment = settingsApplication.biometricEnvironment 138 val provider = ViewModelProvider(this[VIEW_MODEL_STORE_OWNER_KEY]!!) 139 FingerprintEnrollViewModel( 140 biometricEnvironment!!.fingerprintManagerInteractor, 141 provider[FingerprintGatekeeperViewModel::class], 142 provider[FingerprintNavigationViewModel::class], 143 ) 144 } 145 } 146 } 147 } 148