1 /*
<lambda>null2  * Copyright (C) 2024 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.domain.interactor
18 
19 import android.content.Context
20 import android.hardware.fingerprint.FingerprintEnrollOptions
21 import android.hardware.fingerprint.FingerprintManager
22 import android.os.CancellationSignal
23 import android.util.Log
24 import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
25 import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
26 import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
27 import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
28 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
29 import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
30 import kotlinx.coroutines.channels.awaitClose
31 import kotlinx.coroutines.channels.onFailure
32 import kotlinx.coroutines.delay
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.MutableStateFlow
35 import kotlinx.coroutines.flow.callbackFlow
36 import kotlinx.coroutines.flow.update
37 
38 /** This repository is responsible for collecting all state related to the enroll API. */
39 interface FingerprintEnrollInteractor {
40 
41   /**
42    * By calling this function, [fingerEnrollState] will begin to be populated with data on success.
43    */
44   suspend fun enroll(
45     hardwareAuthToken: ByteArray?,
46     enrollReason: EnrollReason,
47     fingerprintEnrollOptions: FingerprintEnrollOptions,
48   ): Flow<FingerEnrollState>
49 }
50 
51 class FingerprintEnrollInteractorImpl(
52   private val applicationContext: Context,
53   private val fingerprintManager: FingerprintManager?,
54   private val fingerprintFlow: FingerprintFlow,
55 ) : FingerprintEnrollInteractor {
56   private val enrollRequestOutstanding = MutableStateFlow(false)
57 
enrollnull58   override suspend fun enroll(
59     hardwareAuthToken: ByteArray?,
60     enrollReason: EnrollReason,
61     fingerprintEnrollOptions: FingerprintEnrollOptions,
62   ): Flow<FingerEnrollState> = callbackFlow {
63     // TODO (b/308456120) Improve this logic
64     if (enrollRequestOutstanding.value) {
65       Log.d(TAG, "Outstanding enroll request, waiting 150ms")
66       delay(150)
67       if (enrollRequestOutstanding.value) {
68         Log.e(TAG, "Request still present, continuing")
69       }
70     }
71 
72     enrollRequestOutstanding.update { true }
73 
74     var streamEnded = false
75     var totalSteps: Int? = null
76     val enrollmentCallback =
77       object : FingerprintManager.EnrollmentCallback() {
78         override fun onEnrollmentProgress(remaining: Int) {
79           // This is sort of an implementation detail, but unfortunately the API isn't
80           // very expressive. If anything we should look at changing the FingerprintManager API.
81           if (totalSteps == null) {
82             totalSteps = remaining + 1
83           }
84 
85           trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure { error ->
86             Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
87           }
88 
89           if (remaining == 0) {
90             streamEnded = true
91             enrollRequestOutstanding.update { false }
92           }
93         }
94 
95         override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
96           trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString())).onFailure { error
97             ->
98             Log.d(TAG, "onEnrollmentHelp failed to send, due to $error")
99           }
100         }
101 
102         override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
103           trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard)).onFailure { error ->
104             Log.d(TAG, "onEnrollmentError failed to send, due to $error")
105           }
106           Log.d(TAG, "onEnrollmentError($errMsgId)")
107           streamEnded = true
108           enrollRequestOutstanding.update { false }
109         }
110 
111         override fun onUdfpsPointerDown(sensorId: Int) {
112           trySend(FingerEnrollState.PointerDown(sensorId)).onFailure { error ->
113             Log.d(TAG, "onUdfpsPointerDown failed to send, due to $error")
114           }
115         }
116 
117         override fun onUdfpsPointerUp(sensorId: Int) {
118           trySend(FingerEnrollState.PointerUp(sensorId)).onFailure { error ->
119             Log.d(TAG, "onUdfpsPointerUp failed to send, due to $error")
120           }
121         }
122 
123         override fun onUdfpsOverlayShown() {
124           trySend(FingerEnrollState.OverlayShown).onFailure { error ->
125             Log.d(TAG, "OverlayShown failed to send, due to $error")
126           }
127         }
128 
129         override fun onAcquired(isAcquiredGood: Boolean) {
130           trySend(FingerEnrollState.Acquired(isAcquiredGood)).onFailure { error ->
131             Log.d(TAG, "Acquired failed to send, due to $error")
132           }
133         }
134       }
135 
136     val cancellationSignal = CancellationSignal()
137 
138     fingerprintManager?.enroll(
139       hardwareAuthToken,
140       cancellationSignal,
141       applicationContext.userId,
142       enrollmentCallback,
143       enrollReason.toOriginalReason(),
144       fingerprintEnrollOptions,
145     )
146     awaitClose {
147       // If the stream has not been ended, and the user has stopped collecting the flow
148       // before it was over, send cancel.
149       if (!streamEnded) {
150         Log.e(TAG, "Cancel is sent from settings for enroll()")
151         cancellationSignal.cancel()
152       }
153     }
154   }
155 
156   companion object {
157     private const val TAG = "FingerprintEnrollStateRepository"
158   }
159 }
160