1 /*
2  * Copyright (C) 2022 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.credentialmanager.createflow
18 
19 import android.credentials.flags.Flags.credmanBiometricApiEnabled
20 import android.graphics.drawable.Drawable
21 import com.android.credentialmanager.R
22 import com.android.credentialmanager.model.CredentialType
23 import com.android.credentialmanager.model.EntryInfo
24 import com.android.credentialmanager.model.creation.CreateOptionInfo
25 import com.android.credentialmanager.model.creation.RemoteInfo
26 
27 data class CreateCredentialUiState(
28     val enabledProviders: List<EnabledProviderInfo>,
29     val disabledProviders: List<DisabledProviderInfo>? = null,
30     val currentScreenState: CreateScreenState,
31     val requestDisplayInfo: RequestDisplayInfo,
32     val sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
33     val activeEntry: ActiveEntry? = null,
34     val remoteEntry: RemoteInfo? = null,
35     val foundCandidateFromUserDefaultProvider: Boolean,
36 )
37 
38 /**
39  * Checks if this create flow is a biometric flow. Note that this flow differs slightly from the
40  * autoselect 'get' flow. Namely, given there can be multiple providers, rather than multiple
41  * accounts, the idea is that autoselect is ever only enabled for a single provider (or even, in
42  * that case, a single 'type' (family only, or work only) for a provider). However, for all other
43  * cases, the biometric screen should always show up if that entry contains the biometric bit.
44  */
findBiometricFlowEntrynull45 internal fun findBiometricFlowEntry(
46     activeEntry: ActiveEntry,
47     isAutoSelectFlow: Boolean,
48 ): CreateOptionInfo? {
49     if (!credmanBiometricApiEnabled()) {
50         return null
51     }
52     if (isAutoSelectFlow) {
53         // Since this is the create flow, auto select will only ever be true for a single provider.
54         // However, for all other cases, biometric should be used if that bit is opted into. If
55         // they clash, autoselect is always preferred, but that's only if there's a single provider.
56         return null
57     }
58     val biometricEntry = getCreateEntry(activeEntry)
59     return if (biometricEntry?.biometricRequest != null) biometricEntry else null
60 }
61 
62 /**
63  * Retrieves the activeEntry by validating it is a [CreateOptionInfo]. This is done by ensuring
64  * that the [activeEntry] exists as a [CreateOptionInfo] to retrieve its [EntryInfo].
65  */
getCreateEntrynull66 internal fun getCreateEntry(
67     activeEntry: ActiveEntry?,
68 ): CreateOptionInfo? {
69     val entry = activeEntry?.activeEntryInfo
70     if (entry !is CreateOptionInfo) {
71         return null
72     }
73     return entry
74 }
75 
76 /**
77 * Determines if the flow is a biometric flow by taking into account autoselect criteria.
78 */
isBiometricFlownull79 internal fun isBiometricFlow(
80     activeEntry: ActiveEntry,
81     isAutoSelectFlow: Boolean,
82 ) = findBiometricFlowEntry(activeEntry, isAutoSelectFlow) != null
83 
84 /**
85  * This utility presents the correct resource string for the create flows title conditionally.
86  * Similar to generateDisplayTitleTextResCode in the 'get' flow, but for the create flow instead.
87  * This is for the title, and is a shared resource, unlike the specific unlock request text.
88  * E.g. this will look something like: "Create passkey to sign in to Tribank."
89  * // TODO(b/330396140) : Validate approach and add dynamic auth strings
90  */
91 internal fun getCreateTitleResCode(createRequestDisplayInfo: RequestDisplayInfo): Int =
92     when (createRequestDisplayInfo.type) {
93         CredentialType.PASSKEY ->
94             R.string.choose_create_option_passkey_title
95 
96         CredentialType.PASSWORD ->
97             R.string.choose_create_option_password_title
98 
99         CredentialType.UNKNOWN ->
100             R.string.choose_create_option_sign_in_title
101     }
102 
isFlowAutoSelectablenull103 internal fun isFlowAutoSelectable(
104     uiState: CreateCredentialUiState
105 ): Boolean {
106     return isFlowAutoSelectable(uiState.requestDisplayInfo, uiState.activeEntry,
107         uiState.sortedCreateOptionsPairs)
108 }
109 
110 /**
111  * When initializing, the [CreateCredentialUiState] is generated after the initial screen is set.
112  * This overloaded method allows identifying if the flow is auto selectable prior to the creation
113  * of the [CreateCredentialUiState].
114  */
isFlowAutoSelectablenull115 internal fun isFlowAutoSelectable(
116     requestDisplayInfo: RequestDisplayInfo,
117     activeEntry: ActiveEntry?,
118     sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>
119 ): Boolean {
120     val isAutoSelectRequest = requestDisplayInfo.isAutoSelectRequest
121     if (sortedCreateOptionsPairs.size != 1) {
122         return false
123     }
124     val singleEntry = getCreateEntry(activeEntry)
125     return isAutoSelectRequest && singleEntry?.allowAutoSelect == true
126 }
127 
hasContentToDisplaynull128 internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
129     return state.sortedCreateOptionsPairs.isNotEmpty() ||
130         (!state.requestDisplayInfo.preferImmediatelyAvailableCredentials &&
131             state.remoteEntry != null)
132 }
133 
134 open class ProviderInfo(
135   val icon: Drawable,
136   val id: String,
137   val displayName: String,
138 )
139 
140 class EnabledProviderInfo(
141     icon: Drawable,
142     id: String,
143     displayName: String,
144     // Sorted by last used time
145     var sortedCreateOptions: List<CreateOptionInfo>,
146     var remoteEntry: RemoteInfo?,
147 ) : ProviderInfo(icon, id, displayName)
148 
149 class DisabledProviderInfo(
150   icon: Drawable,
151   id: String,
152   displayName: String,
153 ) : ProviderInfo(icon, id, displayName)
154 
155 data class RequestDisplayInfo(
156   val title: String,
157   val subtitle: String?,
158   val type: CredentialType,
159   val appName: String,
160   val typeIcon: Drawable,
161   val preferImmediatelyAvailableCredentials: Boolean,
162   val appPreferredDefaultProviderId: String?,
163   val userSetDefaultProviderIds: Set<String>,
164   // Whether the given CreateCredentialRequest allows auto select.
165   val isAutoSelectRequest: Boolean,
166 )
167 
168 /**
169  * This is initialized to be the most recent used. Can then be changed if
170  * user selects a different entry on the more option page.
171  */
172 data class ActiveEntry (
173     val activeProvider: EnabledProviderInfo,
174     val activeEntryInfo: EntryInfo,
175 )
176 
177 /** The name of the current screen. */
178 enum class CreateScreenState {
179   CREATION_OPTION_SELECTION,
180   BIOMETRIC_SELECTION,
181   MORE_OPTIONS_SELECTION,
182   DEFAULT_PROVIDER_CONFIRMATION,
183   EXTERNAL_ONLY_SELECTION,
184   MORE_OPTIONS_SELECTION_ONLY,
185 }
186