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