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.enrollment.activity
18
19 import android.app.Activity
20 import android.content.Intent
21 import android.content.res.Configuration
22 import android.hardware.fingerprint.FingerprintEnrollOptions
23 import android.hardware.fingerprint.FingerprintManager
24 import android.os.Bundle
25 import android.util.Log
26 import androidx.activity.result.contract.ActivityResultContracts
27 import androidx.activity.viewModels
28 import androidx.fragment.app.Fragment
29 import androidx.fragment.app.FragmentActivity
30 import androidx.lifecycle.lifecycleScope
31 import com.android.settings.R
32 import com.android.settings.SetupWizardUtils
33 import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
34 import com.android.settings.biometrics.BiometricEnrollBase
35 import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
36 import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
37 import com.android.settings.biometrics.BiometricUtils
38 import com.android.settings.biometrics.GatekeeperPasswordProvider
39 import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
40 import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
41 import com.android.settings.biometrics.fingerprint2.lib.model.Default
42 import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
43 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment
44 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
45 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
46 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.RfpsEnrollFindSensorFragment
47 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.SfpsEnrollFindSensorFragment
48 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.UdfpsEnrollFindSensorFragment
49 import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment
50 import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.fragment.UdfpsEnrollFragment
51 import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsLastStepViewModel
52 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
53 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
54 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
55 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
56 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintFlowViewModel
57 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
58 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.ConfirmDeviceCredential
59 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Confirmation
60 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
61 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Enrollment
62 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Init
63 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Introduction
64 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.TransitionStep
65 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
66 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
67 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Transition
68 import com.android.settings.flags.Flags
69 import com.android.settings.password.ChooseLockGeneric
70 import com.android.settings.password.ChooseLockSettingsHelper
71 import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
72 import com.android.settingslib.display.DisplayDensityUtils
73 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
74 import com.google.android.setupcompat.util.WizardManagerHelper
75 import com.google.android.setupdesign.util.ThemeHelper
76 import kotlinx.coroutines.Dispatchers
77 import kotlinx.coroutines.flow.filterNotNull
78 import kotlinx.coroutines.launch
79
80 private const val TAG = "FingerprintEnrollmentV2Activity"
81
82 /**
83 * This is the activity that controls the entire Fingerprint Enrollment experience through its
84 * children fragments.
85 */
86 class FingerprintEnrollmentV2Activity : FragmentActivity() {
87 private val navigationViewModel: FingerprintNavigationViewModel by viewModels {
88 FingerprintNavigationViewModel.Factory
89 }
90 private val fingerprintFlowViewModel: FingerprintFlowViewModel by viewModels {
91 FingerprintFlowViewModel.Factory
92 }
93
94 private val gatekeeperViewModel: FingerprintGatekeeperViewModel by viewModels {
95 FingerprintGatekeeperViewModel.Factory
96 }
97
98 /**
99 * View models below this line are not used by this class but must be initialized in the activity
100 * view model store to be used by other view models.
101 */
102 private val fingerprintEnrollViewModel: FingerprintEnrollViewModel by viewModels {
103 FingerprintEnrollViewModel.Factory
104 }
105
106 private val fingerprintEnrollEnrollingViewModel:
107 FingerprintEnrollEnrollingViewModel by viewModels {
108 FingerprintEnrollEnrollingViewModel.Factory
109 }
110
111 private val udfpsLastStepViewModel: UdfpsLastStepViewModel by viewModels {
112 UdfpsLastStepViewModel.Factory
113 }
114
115 private val backgroundViewModel: BackgroundViewModel by viewModels { BackgroundViewModel.Factory }
116
117 private lateinit var foldStateInteractor: FoldStateInteractor
118 private lateinit var displayDensityInteractor: DisplayDensityInteractor
119 private val coroutineDispatcher = Dispatchers.Default
120
121 /** Result listener for ChooseLock activity flow. */
122 private val confirmDeviceResultListener =
123 registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
124 val resultCode = result.resultCode
125 val data = result.data
126 onConfirmDevice(resultCode, data)
127 }
128
129 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
130 super.onActivityResult(requestCode, resultCode, data)
131 if (requestCode == CONFIRM_REQUEST) {
132 onConfirmDevice(resultCode, data)
133 }
134 }
135
136 override fun onStop() {
137 super.onStop()
138 if (!isChangingConfigurations) {
139 backgroundViewModel.wentToBackground()
140 }
141 }
142
143 override fun onResume() {
144 super.onResume()
145 backgroundViewModel.inForeground()
146 }
147
148 override fun onConfigurationChanged(newConfig: Configuration) {
149 super.onConfigurationChanged(newConfig)
150 foldStateInteractor.onConfigurationChange(newConfig)
151 val displayDensityUtils = DisplayDensityUtils(applicationContext)
152 val currIndex = displayDensityUtils.currentIndex
153 displayDensityInteractor.updateFontScale(resources.configuration.fontScale)
154 displayDensityUtils.values?.let {
155 displayDensityInteractor.updateDisplayDensity(it[currIndex])
156 }
157 }
158
159 private fun onConfirmDevice(resultCode: Int, data: Intent?) {
160 val wasSuccessful = resultCode == RESULT_FINISHED || resultCode == Activity.RESULT_OK
161 val gateKeeperPasswordHandle = data?.getExtra(EXTRA_KEY_GK_PW_HANDLE) as Long
162
163 lifecycleScope.launch {
164 val confirmDeviceResult =
165 if (wasSuccessful) {
166 FingerprintAction.CONFIRM_DEVICE_SUCCESS
167 } else {
168 FingerprintAction.CONFIRM_DEVICE_FAIL
169 }
170 gatekeeperViewModel.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
171 navigationViewModel.update(
172 confirmDeviceResult,
173 ConfirmDeviceCredential::class,
174 "$TAG#onConfirmDevice",
175 )
176 }
177 }
178
179 override fun onCreate(savedInstanceState: Bundle?) {
180 super.onCreate(savedInstanceState)
181 // TODO(b/299573056): Show split screen dialog when it's in multi window mode.
182 setContentView(R.layout.fingerprint_v2_enroll_main)
183
184 if (!Flags.fingerprintV2Enrollment()) {
185 check(false) {
186 "fingerprint enrollment v2 is not enabled, " +
187 "please run adb shell device_config put " +
188 "biometrics_framework com.android.settings.flags.fingerprint_v2_enrollment true"
189 }
190 finish()
191 }
192
193 // Ensure that these view models are actually created and in this order
194 navigationViewModel
195 fingerprintFlowViewModel
196 gatekeeperViewModel
197 fingerprintEnrollViewModel
198 backgroundViewModel
199 fingerprintEnrollEnrollingViewModel
200 udfpsLastStepViewModel
201
202 setTheme(SetupWizardUtils.getTheme(applicationContext, intent))
203 ThemeHelper.trySetDynamicColor(applicationContext)
204
205 val backgroundDispatcher = Dispatchers.IO
206
207 val context = applicationContext
208 val fingerprintManager = context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager
209 val isAnySuw = WizardManagerHelper.isAnySetupWizard(intent)
210 val enrollType =
211 if (isAnySuw) {
212 SetupWizard
213 } else {
214 Default
215 }
216
217 fingerprintFlowViewModel.updateFlowType(enrollType)
218
219 if (intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) === -1) {
220 val isSuw: Boolean = WizardManagerHelper.isAnySetupWizard(intent)
221 intent.putExtra(
222 BiometricUtils.EXTRA_ENROLL_REASON,
223 if (isSuw) FingerprintEnrollOptions.ENROLL_REASON_SUW
224 else FingerprintEnrollOptions.ENROLL_REASON_SETTINGS,
225 )
226 }
227
228 var challenge = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
229 val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
230 val gatekeeperInfo = FingerprintGatekeeperViewModel.toGateKeeperInfo(challenge, token)
231
232 val hasConfirmedDeviceCredential = gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo
233
234 navigationViewModel.updateFingerprintFlow(enrollType)
235 navigationViewModel.hasConfirmedDeviceCredential(hasConfirmedDeviceCredential)
236
237 lifecycleScope.launch {
238 navigationViewModel.currentStep.collect { step ->
239 if (step is Init) {
240 navigationViewModel.update(FingerprintAction.ACTIVITY_CREATED, Init::class, "$TAG#init")
241 }
242 }
243 }
244
245 lifecycleScope.launch {
246 navigationViewModel.navigateTo.filterNotNull().collect { step ->
247 Log.d(TAG, "navigateTo: $step")
248 if (step is ConfirmDeviceCredential) {
249 launchConfirmOrChooseLock(userId)
250 navigationViewModel.update(
251 FingerprintAction.TRANSITION_FINISHED,
252 TransitionStep::class,
253 "$TAG#launchConfirmOrChooseLock",
254 )
255 } else {
256 val theClass: Fragment? =
257 when (step) {
258 Confirmation -> FingerprintEnrollConfirmationV2Fragment()
259 is Education -> {
260 when (step.sensor.sensorType) {
261 FingerprintSensorType.REAR -> RfpsEnrollFindSensorFragment()
262 FingerprintSensorType.UDFPS_OPTICAL,
263 FingerprintSensorType.UDFPS_ULTRASONIC -> UdfpsEnrollFindSensorFragment()
264 else -> SfpsEnrollFindSensorFragment()
265 }
266 }
267 is Enrollment -> {
268 when (step.sensor.sensorType) {
269 FingerprintSensorType.REAR -> RFPSEnrollFragment()
270 FingerprintSensorType.UDFPS_OPTICAL,
271 FingerprintSensorType.UDFPS_ULTRASONIC -> UdfpsEnrollFragment()
272 else -> FingerprintEnrollEnrollingV2Fragment()
273 }
274 }
275 is Introduction -> FingerprintEnrollIntroV2Fragment()
276 else -> null
277 }
278
279 if (theClass != null) {
280 supportFragmentManager
281 .beginTransaction()
282 .setCustomAnimations(
283 step.enterTransition.toAnimation(),
284 step.exitTransition.toAnimation(),
285 )
286 .setReorderingAllowed(true)
287 .replace(R.id.fragment_container_view, theClass::class.java, null)
288 .commit()
289 navigationViewModel.update(
290 FingerprintAction.TRANSITION_FINISHED,
291 TransitionStep::class,
292 "$TAG#fragmentManager.add($theClass)",
293 )
294 }
295 }
296 }
297 }
298
299 lifecycleScope.launch {
300 navigationViewModel.shouldFinish.filterNotNull().collect {
301 Log.d(TAG, "FingerprintSettingsNav.finishing($it)")
302 if (it.result != null) {
303 finishActivity(it.result)
304 } else {
305 finish()
306 }
307 }
308 }
309
310 lifecycleScope.launch {
311 navigationViewModel.currentScreen.filterNotNull().collect { screen ->
312 Log.d(TAG, "FingerprintSettingsNav.currentScreen($screen)")
313 }
314 }
315
316 val fromSettingsSummary =
317 intent.getBooleanExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, false)
318 if (
319 fromSettingsSummary && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)
320 ) {
321 overridePendingTransition(
322 com.google.android.setupdesign.R.anim.sud_slide_next_in,
323 com.google.android.setupdesign.R.anim.sud_slide_next_out,
324 )
325 }
326 }
327
328 private fun launchConfirmOrChooseLock(userId: Int) {
329 val activity = this
330 lifecycleScope.launch(coroutineDispatcher) {
331 val intent = Intent()
332 val builder = ChooseLockSettingsHelper.Builder(activity)
333 val launched =
334 builder
335 .setRequestCode(CONFIRM_REQUEST)
336 .setTitle(getString(R.string.security_settings_fingerprint_preference_title))
337 .setRequestGatekeeperPasswordHandle(true)
338 .setUserId(userId)
339 .setForegroundOnly(true)
340 .setReturnCredentials(true)
341 .show()
342 if (!launched) {
343 intent.setClassName(SETTINGS_PACKAGE_NAME, ChooseLockGeneric::class.java.name)
344 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true)
345 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true)
346 intent.putExtra(Intent.EXTRA_USER_ID, userId)
347 confirmDeviceResultListener.launch(intent)
348 }
349 }
350 }
351 }
352
Transitionnull353 private fun Transition.toAnimation(): Int {
354 return when (this) {
355 Transition.EnterFromLeft -> com.google.android.setupdesign.R.anim.sud_slide_back_in
356 Transition.EnterFromRight -> com.google.android.setupdesign.R.anim.sud_slide_next_in
357 Transition.ExitToLeft -> com.google.android.setupdesign.R.anim.sud_slide_next_out
358 Transition.ExitToRight -> com.google.android.setupdesign.R.anim.sud_slide_back_out
359 }
360 }
361