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