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