Merge "Update the logic and storage about isFirstTimeUse and defaultProvider"

This commit is contained in:
Qinmei Du
2022-12-14 03:56:55 +00:00
committed by Android (Google) Code Review
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,
)
/**