1 /*
2  * 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.viewmodel
18 
19 import com.android.settings.biometrics.fingerprint2.lib.model.FastEnroll
20 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
21 import com.android.systemui.biometrics.shared.model.FingerprintSensor
22 
23 /**
24  * A [FingerprintAction] event notifies the current [FingerprintNavigationStep] that an event
25  * occurred. Depending on the type of [FingerprintAction] and the current
26  * [FingerprintNavigationStep], the navstep will potentially produce a new
27  * [FingerprintNavigationStep] indicating either 1). Control flow has changed 2). The activity has
28  * finished 3). A transition is required
29  */
30 enum class FingerprintAction {
31   NEXT,
32   PREV,
33   CONFIRM_DEVICE_SUCCESS,
34   CONFIRM_DEVICE_FAIL,
35   TRANSITION_FINISHED,
36   DID_GO_TO_BACKGROUND,
37   ACTIVITY_CREATED,
38   NEGATIVE_BUTTON_PRESSED,
39   USER_CLICKED_FINISH,
40   ADD_ANOTHER,
41 }
42 
43 /** State that can be used to help a [FingerprintNavigationStep] determine the next step to take. */
44 data class NavigationState(
45   val flowType: FingerprintFlow,
46   val hasConfirmedDeviceCredential: Boolean,
47   val fingerprintSensor: FingerprintSensor?,
48 )
49 
50 /**
51  * A generic interface for operating on (state, action) -> state? which will produce either another
52  * FingerprintNavStep if something is required, or nothing.
53  *
54  * Note during the lifetime of the Activity, their should only be one [FingerprintNavigationStep] at
55  * a time.
56  */
57 sealed interface FingerprintNavigationStep {
updatenull58   fun update(state: NavigationState, action: FingerprintAction): FingerprintNavigationStep?
59 
60   /**
61    * This indicates that a transition should occur from one screen to another. This class should
62    * contain all necessary info about the transition.
63    *
64    * A transition step will cause a screen to change ownership from the current screen to the
65    * [nextUiStep], after the transition has been completed and a
66    * [FingerprintAction.TRANSITION_FINISHED] has been sent, the [nextUiStep] will be given control.
67    */
68   class TransitionStep(val nextUiStep: UiStep) : FingerprintNavigationStep {
69     override fun update(
70       state: NavigationState,
71       action: FingerprintAction,
72     ): FingerprintNavigationStep? {
73       return when (action) {
74         FingerprintAction.TRANSITION_FINISHED -> nextUiStep
75         else -> null
76       }
77     }
78 
79     override fun toString(): String = "TransitionStep(nextUiStep=$nextUiStep)"
80   }
81 
82   /** Indicates we should finish the enrolling activity */
83   data class Finish(val result: Int?) : FingerprintNavigationStep {
updatenull84     override fun update(
85       state: NavigationState,
86       action: FingerprintAction,
87     ): FingerprintNavigationStep? = null
88   }
89 
90   /** UiSteps should have a 1 to 1 mapping between each screen of FingerprintEnrollment */
91   sealed class UiStep(
92     val enterTransition: Transition = Transition.EnterFromRight,
93     val exitTransition: Transition = Transition.ExitToLeft,
94   ) : FingerprintNavigationStep
95 
96   /** This is the landing page for enrollment, where no content is shown. */
97   data object Init : UiStep() {
98     override fun update(
99       state: NavigationState,
100       action: FingerprintAction,
101     ): FingerprintNavigationStep? {
102       return when (action) {
103         FingerprintAction.ACTIVITY_CREATED -> {
104           if (!state.hasConfirmedDeviceCredential) {
105             TransitionStep(ConfirmDeviceCredential)
106           } else if (state.flowType is FastEnroll) {
107             TransitionStep(Enrollment(state.fingerprintSensor!!))
108           } else {
109             TransitionStep(Introduction())
110           }
111         }
112         else -> null
113       }
114     }
115   }
116 
117   /** Indicates the ConfirmDeviceCredential activity is being presented to the user */
118   data object ConfirmDeviceCredential : UiStep() {
updatenull119     override fun update(
120       state: NavigationState,
121       action: FingerprintAction,
122     ): FingerprintNavigationStep? {
123       return when (action) {
124         FingerprintAction.CONFIRM_DEVICE_SUCCESS -> TransitionStep(Introduction())
125         FingerprintAction.CONFIRM_DEVICE_FAIL -> Finish(null)
126         else -> null
127       }
128     }
129   }
130 
131   /** Indicates the FingerprintIntroduction screen is being presented to the user */
132   class Introduction(
133     enterTransition: Transition = Transition.EnterFromRight,
134     exitTransition: Transition = Transition.ExitToLeft,
135   ) : UiStep(enterTransition, exitTransition) {
updatenull136     override fun update(
137       state: NavigationState,
138       action: FingerprintAction,
139     ): FingerprintNavigationStep? {
140       return when (action) {
141         FingerprintAction.NEXT -> TransitionStep(Education(state.fingerprintSensor!!))
142         FingerprintAction.NEGATIVE_BUTTON_PRESSED,
143         FingerprintAction.PREV -> Finish(null)
144         else -> null
145       }
146     }
147   }
148 
149   /** Indicates the FingerprintEducation screen is being presented to the user */
150   class Education(
151     val sensor: FingerprintSensor,
152     enterTransition: Transition = Transition.EnterFromRight,
153     exitTransition: Transition = Transition.ExitToLeft,
154   ) : UiStep(enterTransition, exitTransition) {
updatenull155     override fun update(
156       state: NavigationState,
157       action: FingerprintAction,
158     ): FingerprintNavigationStep? {
159       return when (action) {
160         FingerprintAction.NEXT -> TransitionStep(Enrollment(state.fingerprintSensor!!))
161         FingerprintAction.NEGATIVE_BUTTON_PRESSED,
162         FingerprintAction.PREV ->
163           TransitionStep(Introduction(Transition.EnterFromLeft, Transition.ExitToRight))
164         else -> null
165       }
166     }
167   }
168 
169   /** Indicates the Enrollment screen is being presented to the user */
170   data class Enrollment(val sensor: FingerprintSensor) : UiStep() {
updatenull171     override fun update(
172       state: NavigationState,
173       action: FingerprintAction,
174     ): FingerprintNavigationStep? {
175       return when (action) {
176         FingerprintAction.NEXT -> TransitionStep(Confirmation)
177         FingerprintAction.NEGATIVE_BUTTON_PRESSED,
178         FingerprintAction.USER_CLICKED_FINISH,
179         FingerprintAction.DID_GO_TO_BACKGROUND -> Finish(null)
180         else -> null
181       }
182     }
183   }
184 
185   /** Indicates the Confirmation screen is being presented to the user */
186   data object Confirmation : UiStep() {
updatenull187     override fun update(
188       state: NavigationState,
189       action: FingerprintAction,
190     ): FingerprintNavigationStep? {
191       return when (action) {
192         FingerprintAction.NEXT -> Finish(null)
193         FingerprintAction.PREV ->
194           TransitionStep(
195             Education(state.fingerprintSensor!!, Transition.EnterFromLeft, Transition.ExitToRight)
196           )
197         FingerprintAction.ADD_ANOTHER -> TransitionStep(Enrollment(state.fingerprintSensor!!))
198         else -> null
199       }
200     }
201   }
202 }
203