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 17 package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel 18 19 import android.os.CountDownTimer 20 import android.util.Log 21 import androidx.lifecycle.ViewModel 22 import androidx.lifecycle.ViewModelProvider 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 kotlinx.coroutines.flow.Flow 29 import kotlinx.coroutines.flow.MutableStateFlow 30 import kotlinx.coroutines.flow.asStateFlow 31 import kotlinx.coroutines.flow.map 32 import kotlinx.coroutines.flow.update 33 import kotlinx.coroutines.launch 34 35 sealed interface GatekeeperInfo { 36 object Invalid : GatekeeperInfo 37 38 object Timeout : GatekeeperInfo 39 40 data class GatekeeperPasswordInfo(val token: ByteArray?, val passwordHandle: Long?) : 41 GatekeeperInfo 42 } 43 44 /** 45 * This class is responsible for maintaining the gatekeeper information including things like 46 * timeouts. 47 * 48 * Please note, that this class can't fully support timeouts of the gatekeeper password handle due 49 * to the fact that a handle may have been generated earlier in the settings enrollment and passed 50 * in as a parameter to this class. 51 */ 52 class FingerprintGatekeeperViewModel( 53 private val fingerprintManagerInteractor: FingerprintManagerInteractor 54 ) : ViewModel() { 55 56 private var _gatekeeperInfo: MutableStateFlow<GatekeeperInfo?> = MutableStateFlow(null) 57 58 /** The gatekeeper info for fingerprint enrollment. */ 59 val gatekeeperInfo: Flow<GatekeeperInfo?> = _gatekeeperInfo.asStateFlow() 60 61 /** Indicates if the gatekeeper info is valid. */ 62 val hasValidGatekeeperInfo: Flow<Boolean> = <lambda>null63 gatekeeperInfo.map { it is GatekeeperInfo.GatekeeperPasswordInfo } 64 65 private var countDownTimer: CountDownTimer? = null 66 67 /** Timeout of 15 minutes for a generated challenge */ 68 private val TIMEOUT: Long = 15 * 60 * 1000 69 70 /** Called after a confirm device credential attempt has been made. */ onConfirmDevicenull71 fun onConfirmDevice( 72 wasSuccessful: Boolean, 73 theGatekeeperPasswordHandle: Long?, 74 shouldStartTimer: Boolean = true, 75 ) { 76 if (!wasSuccessful) { 77 Log.d(TAG, "confirmDevice failed") 78 _gatekeeperInfo.update { GatekeeperInfo.Invalid } 79 } else { 80 viewModelScope.launch { 81 val res = fingerprintManagerInteractor.generateChallenge(theGatekeeperPasswordHandle!!) 82 _gatekeeperInfo.update { GatekeeperInfo.GatekeeperPasswordInfo(res.second, res.first) } 83 if (shouldStartTimer) { 84 startTimeout() 85 } 86 } 87 } 88 } 89 startTimeoutnull90 private fun startTimeout() { 91 countDownTimer?.cancel() 92 countDownTimer = 93 object : CountDownTimer(TIMEOUT, 1000) { 94 override fun onFinish() { 95 _gatekeeperInfo.update { GatekeeperInfo.Timeout } 96 } 97 98 override fun onTick(millisUntilFinished: Long) {} 99 } 100 } 101 102 companion object { 103 private const val TAG = "FingerprintGatekeeperViewModel" 104 105 /** 106 * A function that checks if the challenge and token are valid, in which case a 107 * [GatekeeperInfo.GatekeeperPasswordInfo] is provided, else [GatekeeperInfo.Invalid] 108 */ toGateKeeperInfonull109 fun toGateKeeperInfo(challenge: Long?, token: ByteArray?): GatekeeperInfo { 110 Log.d(TAG, "toGateKeeperInfo(${challenge == null}, ${token == null})") 111 if (challenge == null || token == null) { 112 return GatekeeperInfo.Invalid 113 } 114 return GatekeeperInfo.GatekeeperPasswordInfo(token, challenge) 115 } 116 <lambda>null117 val Factory: ViewModelProvider.Factory = viewModelFactory { 118 initializer { 119 val settingsApplication = 120 this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as SettingsApplication 121 val biometricEnvironment = settingsApplication.biometricEnvironment 122 FingerprintGatekeeperViewModel(biometricEnvironment!!.fingerprintManagerInteractor) 123 } 124 } 125 } 126 } 127