• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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