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