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:
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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?,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user