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 
17 package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
18 
19 import android.util.Log
20 import androidx.lifecycle.ViewModel
21 import androidx.lifecycle.ViewModelProvider
22 import androidx.lifecycle.viewModelScope
23 import androidx.lifecycle.viewmodel.initializer
24 import androidx.lifecycle.viewmodel.viewModelFactory
25 import com.android.settings.SettingsApplication
26 import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
27 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
28 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Finish
29 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.TransitionStep
30 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.UiStep
31 import java.lang.NullPointerException
32 import kotlin.reflect.KClass
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.MutableStateFlow
35 import kotlinx.coroutines.flow.SharingStarted
36 import kotlinx.coroutines.flow.StateFlow
37 import kotlinx.coroutines.flow.asStateFlow
38 import kotlinx.coroutines.flow.combine
39 import kotlinx.coroutines.flow.combineTransform
40 import kotlinx.coroutines.flow.filterNotNull
41 import kotlinx.coroutines.flow.stateIn
42 import kotlinx.coroutines.flow.update
43 
44 /**
45  * This class is essentially a wrapper around [FingerprintNavigationStep] that will be used by
46  * fragments/viewmodels that want to consume these events. It should provide no additional
47  * functionality beyond what is available in [FingerprintNavigationStep].
48  */
49 class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
50   ViewModel() {
51 
52   private val _flowInternal: MutableStateFlow<FingerprintFlow?> = MutableStateFlow(null)
53   private val _hasConfirmedDeviceCredential: MutableStateFlow<Boolean> = MutableStateFlow(false)
54   private val _navStateInternal: StateFlow<NavigationState?> =
55     combine(
56         _flowInternal,
57         _hasConfirmedDeviceCredential,
58         fingerprintManagerInteractor.sensorPropertiesInternal,
59       ) { flow, hasConfirmed, sensorType ->
60         if (flow == null || sensorType == null) {
61           return@combine null
62         }
63         return@combine NavigationState(flow, hasConfirmed, sensorType)
64       }
65       .stateIn(viewModelScope, SharingStarted.Eagerly, null)
66 
67   private var _currentStep =
68     MutableStateFlow<FingerprintNavigationStep?>(FingerprintNavigationStep.Init)
69 
70   private var _navigateTo: MutableStateFlow<UiStep?> = MutableStateFlow(null)
71   val navigateTo: Flow<UiStep?> = _navigateTo.asStateFlow()
72 
73   /**
74    * This indicates a navigation event should occur. Navigation depends on navStateInternal being
75    * present.
76    */
77   val currentStep: Flow<FingerprintNavigationStep> =
78     _currentStep.filterNotNull().combineTransform(_navStateInternal.filterNotNull()) { navigation, _
79       ->
80       emit(navigation)
81     }
82 
83   private var _finishState = MutableStateFlow<Finish?>(null)
84 
85   /** This indicates the activity should finish. */
86   val shouldFinish: Flow<Finish?> = _finishState.asStateFlow()
87 
88   private var _currentScreen = MutableStateFlow<UiStep?>(null)
89 
90   /** This indicates what screen should currently be presenting to the user. */
91   val currentScreen: Flow<UiStep?> = _currentScreen.asStateFlow()
92 
93   /** Updates the type of flow the navigation should begin */
94   fun updateFingerprintFlow(flow: FingerprintFlow) {
95     _flowInternal.update { flow }
96   }
97 
98   /** Indicates if we have confirmed device credential */
99   fun hasConfirmedDeviceCredential(hasConfirmedDeviceCredential: Boolean) {
100     _hasConfirmedDeviceCredential.update { hasConfirmedDeviceCredential }
101   }
102 
103   /** See [updateInternal] for more details */
104   fun update(action: FingerprintAction, caller: KClass<*>, debugStr: String) {
105     Log.d(TAG, "$caller.update($action) $debugStr")
106     val currentStep = _currentStep.value
107     val isUiStep = currentStep is UiStep && caller is UiStep
108     if (currentStep == null) {
109       throw NullPointerException("current step is null")
110     }
111     if (isUiStep && currentStep::class != caller) {
112       throw IllegalAccessError(
113         "Error $currentStep != $caller, $caller should not be sending any events at this time"
114       )
115     }
116     val navState = _navStateInternal.value
117     if (navState == null) {
118       throw NullPointerException("nav state is null")
119     }
120     val nextStep = currentStep.update(navState, action) ?: return
121     Log.d(TAG, "nextStep=$nextStep")
122     // Whenever an state update occurs, everything should be cleared.
123     _currentStep.update { nextStep }
124     _finishState.update { null }
125     _currentScreen.update { null }
126 
127     when (nextStep) {
128       is TransitionStep -> {
129         _navigateTo.update { nextStep.nextUiStep }
130       }
131       is Finish -> {
132         _finishState.update { nextStep }
133       }
134       is UiStep -> {
135         _currentScreen.update { nextStep }
136       }
137     }
138   }
139 
140   companion object {
141     private const val TAG = "FingerprintNavigationViewModel"
142     val Factory: ViewModelProvider.Factory = viewModelFactory {
143       initializer {
144         val settingsApplication =
145           this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as SettingsApplication
146         val biometricEnvironment = settingsApplication.biometricEnvironment
147         FingerprintNavigationViewModel(biometricEnvironment!!.fingerprintManagerInteractor)
148       }
149     }
150   }
151 }
152