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