Update the logic and storage about isFirstTimeUse and defaultProvider

1. Store isDefault provider and the first time use in local ui state
2. Fix the issue that the default provider should not just be null if
we see the passkey intro screen. (The situation for when we first time
see passkey but already set a default provider) And simply the code
structure

Test: deployed locally

Bug: 262215666
Change-Id: I9ab3752c709e528289442f8514e747bb68348a0b
This commit is contained in:
Qinmei Du
2022-12-12 11:58:30 +00:00
parent 016f2243f9
commit 53270521bb
7 changed files with 163 additions and 73 deletions

View File

@@ -41,8 +41,6 @@ import android.os.Bundle
import android.os.ResultReceiver
import android.service.credentials.CredentialProviderService
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RemoteInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
@@ -63,7 +61,7 @@ class CredentialManagerRepo(
requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
) ?: testGetRequestInfo()
) ?: testCreatePasskeyRequestInfo()
providerEnabledList = when (requestInfo.type) {
RequestInfo.TYPE_CREATE ->
@@ -101,7 +99,7 @@ class CredentialManagerRepo(
}
fun onOptionSelected(
providerPackageName: String,
providerId: String,
entryKey: String,
entrySubkey: String,
resultCode: Int? = null,
@@ -109,7 +107,7 @@ class CredentialManagerRepo(
) {
val userSelectionDialogResult = UserSelectionDialogResult(
requestInfo.token,
providerPackageName,
providerId,
entryKey,
entrySubkey,
if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
@@ -138,36 +136,15 @@ class CredentialManagerRepo(
val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
// Handle runtime cast error
providerDisabledList, context)
var defaultProvider: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
var createOptionSize = 0
var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {defaultProvider = providerInfo}
if (providerInfo.remoteEntry != null) {
remoteEntry = providerInfo.remoteEntry!!
}
if (providerInfo.createOptions.isNotEmpty()) {
createOptionSize += providerInfo.createOptions.size
lastSeenProviderWithNonEmptyCreateOptions = providerInfo
}
}
return CreateCredentialUiState(
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
CreateFlowUtils.toCreateScreenState(
createOptionSize, false,
requestDisplayInfo, defaultProvider, remoteEntry),
requestDisplayInfo,
false,
CreateFlowUtils.toActiveEntry(
/*defaultProvider=*/defaultProvider, createOptionSize,
lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
)
return CreateFlowUtils.toCreateCredentialUiState(
providerEnabledList, providerDisabledList, requestDisplayInfo, false)
}
companion object {
// TODO: find a way to resolve this static field leak problem
lateinit var repo: CredentialManagerRepo
fun setup(
@@ -198,7 +175,6 @@ class CredentialManagerRepo(
.setRemoteEntry(
newRemoteEntry("key2", "subkey-1")
)
.setIsDefaultProvider(true)
.build(),
CreateCredentialProviderData
.Builder("com.dashlane")

View File

@@ -43,6 +43,7 @@ class CredentialSelectorActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
CredentialManagerRepo.setup(this, intent)
UserConfigRepo.setup(this)
val requestInfo = CredentialManagerRepo.getInstance().requestInfo
setContent {
CredentialSelectorTheme {

View File

@@ -31,6 +31,8 @@ import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.DisabledProviderInfo
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.getflow.ActionEntryInfo
import com.android.credentialmanager.getflow.AuthenticationEntryInfo
import com.android.credentialmanager.getflow.CredentialEntryInfo
@@ -208,14 +210,13 @@ class CreateFlowUtils {
val pkgInfo = packageManager
.getPackageInfo(packageName!!,
PackageManager.PackageInfoFlags.of(0))
com.android.credentialmanager.createflow.EnabledProviderInfo(
EnabledProviderInfo(
// TODO: decide what to do when failed to load a provider icon
icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
name = it.providerFlattenedComponentName,
displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
createOptions = toCreationOptionInfoList(
it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
isDefault = it.isDefaultProvider,
remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
)
}
@@ -256,8 +257,7 @@ class CreateFlowUtils {
createCredentialRequestJetpack.password,
createCredentialRequestJetpack.type,
requestInfo.appPackageName,
context.getDrawable(R.drawable.ic_password)!!,
requestInfo.isFirstUsage
context.getDrawable(R.drawable.ic_password)!!
)
}
is CreatePublicKeyCredentialRequest -> {
@@ -275,8 +275,7 @@ class CreateFlowUtils {
displayName,
createCredentialRequestJetpack.type,
requestInfo.appPackageName,
context.getDrawable(R.drawable.ic_passkey)!!,
requestInfo.isFirstUsage)
context.getDrawable(R.drawable.ic_passkey)!!)
}
// TODO: correctly parsing for other sign-ins
else -> {
@@ -285,20 +284,60 @@ class CreateFlowUtils {
"Elisa Beckett",
"other-sign-ins",
requestInfo.appPackageName,
context.getDrawable(R.drawable.ic_other_sign_in)!!,
requestInfo.isFirstUsage)
context.getDrawable(R.drawable.ic_other_sign_in)!!)
}
}
}
fun toCreateScreenState(
fun toCreateCredentialUiState(
enabledProviders: List<EnabledProviderInfo>,
disabledProviders: List<DisabledProviderInfo>?,
requestDisplayInfo: RequestDisplayInfo,
isOnPasskeyIntroStateAlready: Boolean,
): CreateCredentialUiState {
var createOptionSize = 0
var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
var defaultProvider: EnabledProviderInfo? = null
val defaultProviderId = UserConfigRepo.getInstance().getDefaultProviderId()
enabledProviders.forEach {
enabledProvider ->
if (defaultProviderId != null) {
if (enabledProvider.name == defaultProviderId) {
defaultProvider = enabledProvider
}
}
if (enabledProvider.createOptions.isNotEmpty()) {
createOptionSize += enabledProvider.createOptions.size
lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
}
if (enabledProvider.remoteEntry != null) {
remoteEntry = enabledProvider.remoteEntry!!
}
}
return CreateCredentialUiState(
enabledProviders = enabledProviders,
disabledProviders = disabledProviders,
toCreateScreenState(
createOptionSize, isOnPasskeyIntroStateAlready,
requestDisplayInfo, defaultProvider, remoteEntry),
requestDisplayInfo,
isOnPasskeyIntroStateAlready,
toActiveEntry(
/*defaultProvider=*/defaultProvider, createOptionSize,
lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
)
}
private fun toCreateScreenState(
createOptionSize: Int,
isOnPasskeyIntroStateAlready: Boolean,
requestDisplayInfo: RequestDisplayInfo,
defaultProvider: EnabledProviderInfo?,
remoteEntry: RemoteInfo?,
): CreateScreenState {
return if (requestDisplayInfo.isFirstUsage && requestDisplayInfo
return if (
UserConfigRepo.getInstance().getIsFirstUse() && requestDisplayInfo
.type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
CreateScreenState.PASSKEY_INTRO
} else if (
@@ -313,12 +352,12 @@ class CreateFlowUtils {
} else if (createOptionSize == 0 && remoteEntry != null) {
CreateScreenState.EXTERNAL_ONLY_SELECTION
} else {
// TODO: properly handle error and gracefully finish itself
throw java.lang.IllegalStateException("Empty provider list.")
// TODO: properly handle error and gracefully finish itself
throw java.lang.IllegalStateException("Empty provider list.")
}
}
fun toActiveEntry(
private fun toActiveEntry(
defaultProvider: EnabledProviderInfo?,
createOptionSize: Int,
lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.credentialmanager
import android.content.Context
import android.content.SharedPreferences
class UserConfigRepo(context: Context) {
val sharedPreferences: SharedPreferences = context.getSharedPreferences(
context.packageName, Context.MODE_PRIVATE)
fun setDefaultProvider(
providerId: String
) {
sharedPreferences.edit().apply {
putString(DEFAULT_PROVIDER, providerId)
apply()
}
}
fun setIsFirstUse(
isFirstUse: Boolean
) {
sharedPreferences.edit().apply {
putBoolean(IS_PASSKEY_FIRST_USE, isFirstUse)
apply()
}
}
fun getDefaultProviderId(): String? {
return sharedPreferences.getString(DEFAULT_PROVIDER, null)
}
fun getIsFirstUse(): Boolean {
return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
}
companion object {
lateinit var repo: UserConfigRepo
const val DEFAULT_PROVIDER = "default_provider"
// This first use value only applies to passkeys, not related with if generally
// credential manager is first use or not
const val IS_PASSKEY_FIRST_USE = "is_passkey_first_use"
fun setup(
context: Context,
) {
repo = UserConfigRepo(context)
}
fun getInstance(): UserConfigRepo {
return repo
}
}
}

View File

@@ -108,7 +108,8 @@ fun CreateCredentialScreen(
)
CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
providerInfo = uiState.activeEntry?.activeProvider!!,
onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
onChangeDefaultSelected = viewModel::onChangeDefaultSelected,
onUseOnceSelected = viewModel::onUseOnceSelected,
)
CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
@@ -464,7 +465,8 @@ fun MoreOptionsSelectionCard(
@Composable
fun MoreOptionsRowIntroCard(
providerInfo: EnabledProviderInfo,
onDefaultOrNotSelected: () -> Unit,
onChangeDefaultSelected: () -> Unit,
onUseOnceSelected: () -> Unit,
) {
ContainerCard() {
Column() {
@@ -496,11 +498,11 @@ fun MoreOptionsRowIntroCard(
) {
CancelButton(
stringResource(R.string.use_once),
onClick = onDefaultOrNotSelected
onClick = onUseOnceSelected
)
ConfirmButton(
stringResource(R.string.set_as_default),
onClick = onDefaultOrNotSelected
onClick = onChangeDefaultSelected
)
}
Divider(

View File

@@ -29,6 +29,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.android.credentialmanager.CreateFlowUtils
import com.android.credentialmanager.CredentialManagerRepo
import com.android.credentialmanager.UserConfigRepo
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ProviderActivityResult
import com.android.credentialmanager.common.ResultState
@@ -61,32 +62,15 @@ class CreateCredentialViewModel(
}
fun onConfirmIntro() {
var createOptionSize = 0
var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
uiState.enabledProviders.forEach {
enabledProvider ->
if (enabledProvider.createOptions.isNotEmpty()) {
createOptionSize += enabledProvider.createOptions.size
lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
}
if (enabledProvider.remoteEntry != null) {
remoteEntry = enabledProvider.remoteEntry!!
}
}
uiState = uiState.copy(
currentScreenState = CreateFlowUtils.toCreateScreenState(
createOptionSize, true,
uiState.requestDisplayInfo, null, remoteEntry),
showActiveEntryOnly = createOptionSize > 1,
activeEntry = CreateFlowUtils.toActiveEntry(
null, createOptionSize, lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
)
uiState = CreateFlowUtils.toCreateCredentialUiState(
uiState.enabledProviders, uiState.disabledProviders,
uiState.requestDisplayInfo, true)
UserConfigRepo.getInstance().setIsFirstUse(false)
}
fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
return uiState.enabledProviders.single {
it.name.equals(providerName)
it.name == providerName
}
}
@@ -116,6 +100,8 @@ class CreateCredentialViewModel(
showActiveEntryOnly = true,
activeEntry = activeEntry
)
val providerId = uiState.activeEntry?.activeProvider?.name
onDefaultChanged(providerId)
}
fun onDisabledPasswordManagerSelected() {
@@ -127,11 +113,29 @@ class CreateCredentialViewModel(
dialogResult.value = DialogResult(ResultState.CANCELED)
}
fun onDefaultOrNotSelected() {
fun onChangeDefaultSelected() {
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
)
// TODO: implement the if choose as default or not logic later
val providerId = uiState.activeEntry?.activeProvider?.name
onDefaultChanged(providerId)
}
fun onUseOnceSelected() {
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
)
}
fun onDefaultChanged(providerId: String?) {
if (providerId != null) {
Log.d(
"Account Selector", "Default provider changed to: " +
" {provider=$providerId")
UserConfigRepo.getInstance().setDefaultProvider(providerId)
} else {
Log.w("Account Selector", "Null provider is being changed")
}
}
fun onEntrySelected(selectedEntry: EntryInfo) {

View File

@@ -31,7 +31,6 @@ class EnabledProviderInfo(
name: String,
displayName: String,
var createOptions: List<CreateOptionInfo>,
val isDefault: Boolean,
var remoteEntry: RemoteInfo?,
) : ProviderInfo(icon, name, displayName)
@@ -77,7 +76,6 @@ data class RequestDisplayInfo(
val type: String,
val appDomainName: String,
val typeIcon: Drawable,
val isFirstUsage: Boolean,
)
/**