1 /*
2  * 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.content.ComponentName
20 import android.content.Intent
21 import android.content.pm.PackageManager
22 import android.os.Bundle
23 import android.util.Log
24 import androidx.activity.result.ActivityResult
25 import androidx.lifecycle.AndroidViewModel
26 import com.android.settings.biometrics.BiometricEnrollBase
27 import com.android.settings.biometrics.fingerprint.FingerprintEnrollFinish.FINGERPRINT_SUGGESTION_ACTIVITY
28 import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction
29 import com.android.settings.biometrics2.data.repository.FingerprintRepository
30 import com.android.settings.biometrics2.ui.model.EnrollmentRequest
31 import kotlinx.atomicfu.AtomicBoolean
32 import kotlinx.atomicfu.atomic
33 import kotlinx.coroutines.CoroutineScope
34 import kotlinx.coroutines.flow.MutableSharedFlow
35 import kotlinx.coroutines.flow.SharedFlow
36 import kotlinx.coroutines.flow.asSharedFlow
37 import kotlinx.coroutines.launch
38 
39 /**
40  * Fingerprint enrollment view model implementation
41  */
42 class FingerprintEnrollmentViewModel(
43     application: Application,
44     private val fingerprintRepository: FingerprintRepository,
45     val request: EnrollmentRequest
46 ) : AndroidViewModel(application) {
47 
48     val isWaitingActivityResult: AtomicBoolean = atomic(false)
49 
50     private val _setResultFlow = MutableSharedFlow<ActivityResult>()
51     val setResultFlow: SharedFlow<ActivityResult>
52         get() = _setResultFlow.asSharedFlow()
53 
54     var isNewFingerprintAdded = false
55         set(value) {
56             // Only allow changing this value from false to true
57             if (!field) {
58                 field = value
59             }
60         }
61 
62     /**
63      * Get override activity result as current ViewModel status.
64      *
65      * FingerprintEnrollmentActivity supports user enrolls 2nd fingerprint or starts a new flow
66      * through Deferred-SUW, Portal-SUW, or SUW Suggestion. Use a method to get override activity
67      * result instead of putting these if-else on every setResult(), .
68      */
getOverrideActivityResultnull69     fun getOverrideActivityResult(
70         result: ActivityResult,
71         generatingChallengeExtras: Bundle?
72     ): ActivityResult {
73         val newResultCode = if (isNewFingerprintAdded)
74             BiometricEnrollBase.RESULT_FINISHED
75         else if (request.isAfterSuwOrSuwSuggestedAction)
76             BiometricEnrollBase.RESULT_CANCELED
77         else
78             result.resultCode
79 
80         var newData = result.data
81         if (newResultCode == BiometricEnrollBase.RESULT_FINISHED
82             && generatingChallengeExtras != null
83         ) {
84             if (newData == null) {
85                 newData = Intent()
86             }
87             newData.putExtras(generatingChallengeExtras)
88         }
89         return ActivityResult(newResultCode, newData)
90     }
91 
92     /**
93      * Activity calls this method during onPause() to finish itself when back to background.
94      *
95      * @param isActivityFinishing Activity has called finish() or not
96      * @param isChangingConfigurations Activity is finished because of configuration changed or not.
97      */
checkFinishActivityDuringOnPausenull98     fun checkFinishActivityDuringOnPause(
99         isActivityFinishing: Boolean,
100         isChangingConfigurations: Boolean,
101         scope: CoroutineScope
102     ) {
103         if (isChangingConfigurations || isActivityFinishing || request.isSuw
104             || isWaitingActivityResult.value
105         ) {
106             return
107         }
108         scope.launch {
109             _setResultFlow.emit(ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null))
110         }
111     }
112 
113     /**
114      * Get Suw fingerprint count extra for statistics
115      */
<lambda>null116     fun getSuwFingerprintCountExtra(userId: Int) = Bundle().also {
117         it.putInt(
118             SetupFingerprintEnrollIntroduction.EXTRA_FINGERPRINT_ENROLLED_COUNT,
119             fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId)
120         )
121     }
122 
123     /**
124      * Gets the result about fingerprint enrollable
125      */
isMaxEnrolledReachednull126     fun isMaxEnrolledReached(userId: Int): Boolean = with(fingerprintRepository) {
127         maxFingerprints <= getNumOfEnrolledFingerprintsSize(userId)
128     }
129 
130     val canAssumeUdfps: Boolean
131         get() = fingerprintRepository.canAssumeUdfps()
132 
133     val canAssumeSfps: Boolean
134         get() = fingerprintRepository.canAssumeSfps()
135 
136     /**
137      * Update FINGERPRINT_SUGGESTION_ACTIVITY into package manager
138      */
updateFingerprintSuggestionEnableStatenull139     fun updateFingerprintSuggestionEnableState(userId: Int) {
140         // Only show "Add another fingerprint" if the user already enrolled one.
141         // "Add fingerprint" will be shown in the main flow if the user hasn't enrolled any
142         // fingerprints. If the user already added more than one fingerprint, they already know
143         // to add multiple fingerprints so we don't show the suggestion.
144         val state = if (fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId) == 1)
145             PackageManager.COMPONENT_ENABLED_STATE_ENABLED
146         else
147             PackageManager.COMPONENT_ENABLED_STATE_DISABLED
148         getApplication<Application>().packageManager.setComponentEnabledSetting(
149             ComponentName(
150                 getApplication(),
151                 FINGERPRINT_SUGGESTION_ACTIVITY
152             ),
153             state,
154             PackageManager.DONT_KILL_APP
155         )
156         Log.d(TAG, "$FINGERPRINT_SUGGESTION_ACTIVITY enabled state: $state")
157     }
158 
159     companion object {
160         private const val TAG = "FingerprintEnrollmentViewModel"
161     }
162 }
163