1 /* <lambda>null2 * 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.binder 18 19 import android.hardware.fingerprint.FingerprintManager 20 import android.util.Log 21 import androidx.lifecycle.LifecycleCoroutineScope 22 import com.android.settings.biometrics.fingerprint2.ui.binder.FingerprintSettingsViewBinder.FingerprintView 23 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollAdditionalFingerprint 24 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollFirstFingerprint 25 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel 26 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsNavigationViewModel 27 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel 28 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintStateViewModel 29 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel 30 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettings 31 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettingsWithResult 32 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchConfirmDeviceCredential 33 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchedActivity 34 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.PreferenceViewModel 35 import com.android.settings.biometrics.fingerprint2.ui.viewmodel.ShowSettings 36 import kotlinx.coroutines.Dispatchers 37 import kotlinx.coroutines.Job 38 import kotlinx.coroutines.flow.collectLatest 39 import kotlinx.coroutines.flow.filterNotNull 40 import kotlinx.coroutines.launch 41 42 private const val TAG = "FingerprintSettingsViewBinder" 43 44 /** Binds a [FingerprintSettingsViewModel] to a [FingerprintView] */ 45 object FingerprintSettingsViewBinder { 46 47 interface FingerprintView { 48 /** 49 * Helper function to launch fingerprint enrollment(This should be the default behavior when a 50 * user enters their PIN/PATTERN/PASS and no fingerprints are enrolled). 51 */ 52 fun launchFullFingerprintEnrollment( 53 userId: Int, 54 gateKeeperPasswordHandle: Long?, 55 challenge: Long?, 56 challengeToken: ByteArray? 57 ) 58 59 /** Helper to launch an add fingerprint request */ 60 fun launchAddFingerprint(userId: Int, challengeToken: ByteArray?) 61 /** 62 * Helper function that will try and launch confirm lock, if that fails we will prompt user to 63 * choose a PIN/PATTERN/PASS. 64 */ 65 fun launchConfirmOrChooseLock(userId: Int) 66 67 /** Used to indicate that FingerprintSettings is finished. */ 68 fun finish() 69 70 /** Indicates what result should be set for the returning callee */ 71 fun setResultExternal(resultCode: Int) 72 /** Indicates the settings UI should be shown */ 73 fun showSettings(state: FingerprintStateViewModel) 74 /** Indicates that a user has been locked out */ 75 fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error) 76 /** Indicates a fingerprint preference should be highlighted */ 77 suspend fun highlightPref(fingerId: Int) 78 /** Indicates a user should be prompted to delete a fingerprint */ 79 suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintViewModel): Boolean 80 /** Indicates a user should be asked to renae ma dialog */ 81 suspend fun askUserToRenameDialog( 82 fingerprintViewModel: FingerprintViewModel 83 ): Pair<FingerprintViewModel, String>? 84 } 85 86 fun bind( 87 view: FingerprintView, 88 viewModel: FingerprintSettingsViewModel, 89 navigationViewModel: FingerprintSettingsNavigationViewModel, 90 lifecycleScope: LifecycleCoroutineScope, 91 ) { 92 93 /** Result listener for launching enrollments **after** a user has reached the settings page. */ 94 95 // Settings display flow 96 lifecycleScope.launch { 97 viewModel.fingerprintState.filterNotNull().collect { view.showSettings(it) } 98 } 99 100 // Dialog flow 101 lifecycleScope.launch { 102 viewModel.isShowingDialog.collectLatest { 103 if (it == null) { 104 return@collectLatest 105 } 106 when (it) { 107 is PreferenceViewModel.RenameDialog -> { 108 val willRename = view.askUserToRenameDialog(it.fingerprintViewModel) 109 if (willRename != null) { 110 Log.d(TAG, "renaming fingerprint $it") 111 viewModel.renameFingerprint(willRename.first, willRename.second) 112 } 113 viewModel.onRenameDialogFinished() 114 } 115 is PreferenceViewModel.DeleteDialog -> { 116 if (view.askUserToDeleteDialog(it.fingerprintViewModel)) { 117 Log.d(TAG, "deleting fingerprint $it") 118 viewModel.deleteFingerprint(it.fingerprintViewModel) 119 } 120 viewModel.onDeleteDialogFinished() 121 } 122 } 123 } 124 } 125 126 // Auth flow 127 lifecycleScope.launch { 128 viewModel.authFlow.filterNotNull().collect { 129 when (it) { 130 is FingerprintAuthAttemptViewModel.Success -> { 131 view.highlightPref(it.fingerId) 132 } 133 is FingerprintAuthAttemptViewModel.Error -> { 134 if (it.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) { 135 view.userLockout(it) 136 } 137 } 138 } 139 } 140 } 141 142 // Launch this on Dispatchers.Default and not main. 143 // Otherwise it takes too long for state transitions such as PIN/PATTERN/PASS 144 // to enrollment, which makes gives the user a janky experience. 145 lifecycleScope.launch(Dispatchers.Default) { 146 var settingsShowingJob: Job? = null 147 navigationViewModel.nextStep.filterNotNull().collect { nextStep -> 148 settingsShowingJob?.cancel() 149 settingsShowingJob = null 150 Log.d(TAG, "next step = $nextStep") 151 when (nextStep) { 152 is EnrollFirstFingerprint -> 153 view.launchFullFingerprintEnrollment( 154 nextStep.userId, 155 nextStep.gateKeeperPasswordHandle, 156 nextStep.challenge, 157 nextStep.challengeToken 158 ) 159 is EnrollAdditionalFingerprint -> 160 view.launchAddFingerprint(nextStep.userId, nextStep.challengeToken) 161 is LaunchConfirmDeviceCredential -> view.launchConfirmOrChooseLock(nextStep.userId) 162 is FinishSettings -> { 163 Log.d(TAG, "Finishing due to ${nextStep.reason}") 164 view.finish() 165 } 166 is FinishSettingsWithResult -> { 167 Log.d(TAG, "Finishing with result ${nextStep.result} due to ${nextStep.reason}") 168 view.setResultExternal(nextStep.result) 169 view.finish() 170 } 171 is ShowSettings -> Log.d(TAG, "Showing settings") 172 is LaunchedActivity -> Log.d(TAG, "Launched activity, awaiting result") 173 } 174 } 175 } 176 } 177 } 178