Loading packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +6 −30 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 -> Loading Loading @@ -101,7 +99,7 @@ class CredentialManagerRepo( } fun onOptionSelected( providerPackageName: String, providerId: String, entryKey: String, entrySubkey: String, resultCode: Int? = null, Loading @@ -109,7 +107,7 @@ class CredentialManagerRepo( ) { val userSelectionDialogResult = UserSelectionDialogResult( requestInfo.token, providerPackageName, providerId, entryKey, entrySubkey, if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null Loading Loading @@ -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( Loading Loading @@ -198,7 +175,6 @@ class CredentialManagerRepo( .setRemoteEntry( newRemoteEntry("key2", "subkey-1") ) .setIsDefaultProvider(true) .build(), CreateCredentialProviderData .Builder("com.dashlane") Loading packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +1 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +52 −13 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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), ) } Loading Loading @@ -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 -> { Loading @@ -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 -> { Loading @@ -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 ( Loading @@ -318,7 +357,7 @@ class CreateFlowUtils { } } fun toActiveEntry( private fun toActiveEntry( defaultProvider: EnabledProviderInfo?, createOptionSize: Int, lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?, Loading packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt 0 → 100644 +70 −0 Original line number Diff line number Diff line /* * 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 } } } packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +6 −4 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -464,7 +465,8 @@ fun MoreOptionsSelectionCard( @Composable fun MoreOptionsRowIntroCard( providerInfo: EnabledProviderInfo, onDefaultOrNotSelected: () -> Unit, onChangeDefaultSelected: () -> Unit, onUseOnceSelected: () -> Unit, ) { ContainerCard() { Column() { Loading Loading @@ -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( Loading Loading
packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +6 −30 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 -> Loading Loading @@ -101,7 +99,7 @@ class CredentialManagerRepo( } fun onOptionSelected( providerPackageName: String, providerId: String, entryKey: String, entrySubkey: String, resultCode: Int? = null, Loading @@ -109,7 +107,7 @@ class CredentialManagerRepo( ) { val userSelectionDialogResult = UserSelectionDialogResult( requestInfo.token, providerPackageName, providerId, entryKey, entrySubkey, if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null Loading Loading @@ -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( Loading Loading @@ -198,7 +175,6 @@ class CredentialManagerRepo( .setRemoteEntry( newRemoteEntry("key2", "subkey-1") ) .setIsDefaultProvider(true) .build(), CreateCredentialProviderData .Builder("com.dashlane") Loading
packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +1 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading
packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +52 −13 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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), ) } Loading Loading @@ -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 -> { Loading @@ -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 -> { Loading @@ -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 ( Loading @@ -318,7 +357,7 @@ class CreateFlowUtils { } } fun toActiveEntry( private fun toActiveEntry( defaultProvider: EnabledProviderInfo?, createOptionSize: Int, lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?, Loading
packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt 0 → 100644 +70 −0 Original line number Diff line number Diff line /* * 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 } } }
packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +6 −4 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -464,7 +465,8 @@ fun MoreOptionsSelectionCard( @Composable fun MoreOptionsRowIntroCard( providerInfo: EnabledProviderInfo, onDefaultOrNotSelected: () -> Unit, onChangeDefaultSelected: () -> Unit, onUseOnceSelected: () -> Unit, ) { ContainerCard() { Column() { Loading Loading @@ -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( Loading