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.biometrics2.ui.viewmodel
17 
18 import android.app.Application
19 import android.util.Log
20 import androidx.lifecycle.AndroidViewModel
21 import com.android.settings.biometrics2.data.repository.FingerprintRepository
22 import com.android.settings.biometrics2.ui.model.EnrollmentRequest
23 import com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus
24 import com.android.settings.biometrics2.ui.model.FingerprintEnrollable
25 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.CONTINUE_ENROLL
26 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.DONE_AND_FINISH
27 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.SKIP_OR_CANCEL
28 import kotlinx.coroutines.CoroutineScope
29 import kotlinx.coroutines.flow.Flow
30 import kotlinx.coroutines.flow.MutableSharedFlow
31 import kotlinx.coroutines.flow.MutableStateFlow
32 import kotlinx.coroutines.flow.SharedFlow
33 import kotlinx.coroutines.flow.asSharedFlow
34 import kotlinx.coroutines.flow.combine
35 import kotlinx.coroutines.launch
36 
37 /** Fingerprint intro onboarding page view model implementation */
38 class FingerprintEnrollIntroViewModel(
39     application: Application,
40     private val fingerprintRepository: FingerprintRepository,
41     val request: EnrollmentRequest,
42     private val userId: Int
43 ) : AndroidViewModel(application) {
44 
45     /** User's action flow (like clicking Agree, Skip, or Done) */
46     private val _actionFlow = MutableSharedFlow<FingerprintEnrollIntroAction>()
47     val actionFlow: SharedFlow<FingerprintEnrollIntroAction>
48         get() = _actionFlow.asSharedFlow()
49 
50     private fun getEnrollableStatus(): FingerprintEnrollable {
51         val num = fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId)
52         val max =
53             if (request.isSuw && !request.isAfterSuwOrSuwSuggestedAction)
54                 fingerprintRepository.getMaxFingerprintsInSuw(
55                     getApplication<Application>().resources
56                 )
57             else
58                 fingerprintRepository.maxFingerprints
59         return if (num >= max)
60             FingerprintEnrollable.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
61         else
62             FingerprintEnrollable.FINGERPRINT_ENROLLABLE_OK
63     }
64 
65     private val hasScrolledToBottomFlow = MutableStateFlow(HAS_SCROLLED_TO_BOTTOM_DEFAULT)
66     private val enrollableStatusFlow = MutableStateFlow(getEnrollableStatus())
67 
68     /** Enrollable status and hasScrollToBottom live data */
69     val pageStatusFlow: Flow<FingerprintEnrollIntroStatus> =
70         hasScrolledToBottomFlow.combine(enrollableStatusFlow) {
71             hasScrolledToBottom: Boolean, enrollableStatus: FingerprintEnrollable ->
72             FingerprintEnrollIntroStatus(hasScrolledToBottom, enrollableStatus)
73         }
74 
75     fun updateEnrollableStatus(scope: CoroutineScope) {
76         scope.launch {
77             enrollableStatusFlow.emit(getEnrollableStatus())
78         }
79     }
80 
81     /** The first sensor type is UDFPS sensor or not */
82     val canAssumeUdfps: Boolean
83         get() = fingerprintRepository.canAssumeUdfps()
84 
85     /** Update onboarding intro page has scrolled to bottom */
86     fun setHasScrolledToBottom(value: Boolean, scope: CoroutineScope) {
87         scope.launch {
88             hasScrolledToBottomFlow.emit(value)
89         }
90     }
91 
92     /** Get parental consent required or not during enrollment process */
93     val isParentalConsentRequired: Boolean
94         get() = fingerprintRepository.isParentalConsentRequired(getApplication())
95 
96     /** Get fingerprint is disable by admin or not */
97     val isBiometricUnlockDisabledByAdmin: Boolean
98         get() = fingerprintRepository.isDisabledByAdmin(getApplication(), userId)
99 
100     /**
101      * User clicks next button
102      */
103     fun onNextButtonClick(scope: CoroutineScope) {
104         scope.launch {
105             when (val status = enrollableStatusFlow.value) {
106                 FingerprintEnrollable.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX ->
107                     _actionFlow.emit(DONE_AND_FINISH)
108 
109                 FingerprintEnrollable.FINGERPRINT_ENROLLABLE_OK ->
110                     _actionFlow.emit(CONTINUE_ENROLL)
111 
112                 else -> Log.w(TAG, "fail to click next, enrolled:$status")
113             }
114         }
115     }
116 
117     /** User clicks skip/cancel button */
118     fun onSkipOrCancelButtonClick(scope: CoroutineScope) {
119         scope.launch {
120             _actionFlow.emit(SKIP_OR_CANCEL)
121         }
122     }
123 
124     companion object {
125         private const val TAG = "FingerprintEnrollIntroViewModel"
126         private const val HAS_SCROLLED_TO_BOTTOM_DEFAULT = false
127         private val ENROLLABLE_STATUS_DEFAULT = FingerprintEnrollable.FINGERPRINT_ENROLLABLE_UNKNOWN
128     }
129 }
130 
131 enum class FingerprintEnrollIntroAction {
132     /** User clicks 'Done' button on this page */
133     DONE_AND_FINISH,
134     /** User clicks 'Agree' button on this page */
135     CONTINUE_ENROLL,
136     /** User clicks 'Skip' button on this page */
137     SKIP_OR_CANCEL
138 }
139