From 53270521bb9b693e884e3b85a226495d8da3ca4b Mon Sep 17 00:00:00 2001 From: Qinmei Du Date: Mon, 12 Dec 2022 11:58:30 +0000 Subject: [PATCH] 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 --- .../CredentialManagerRepo.kt | 36 ++-------- .../CredentialSelectorActivity.kt | 1 + .../credentialmanager/DataConverter.kt | 65 +++++++++++++---- .../credentialmanager/UserConfigRepo.kt | 70 +++++++++++++++++++ .../createflow/CreateCredentialComponents.kt | 10 +-- .../createflow/CreateCredentialViewModel.kt | 52 +++++++------- .../createflow/CreateModel.kt | 2 - 7 files changed, 163 insertions(+), 73 deletions(-) create mode 100644 packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 4faf00c43d34d..d2b29245517ee 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -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") diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index 6a4c599f682ba..cdff2d4295b03 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -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 { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 56fbf6632dc50..db676b297c1b2 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -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, + disabledProviders: List?, + 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?, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt new file mode 100644 index 0000000000000..5e77663bee2ae --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt @@ -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 + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index c26fac8895abd..38e2caa25a324 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -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( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt index 518aaee5ea9bf..9d029dffad529 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt @@ -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) { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 21abe08c04f27..fda0b979d351e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -31,7 +31,6 @@ class EnabledProviderInfo( name: String, displayName: String, var createOptions: List, - 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, ) /**