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, ) /**