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