From 79fa94fb78e48dde27ec86270cac80c6878b5286 Mon Sep 17 00:00:00 2001 From: shuanghao <shuanghao@google.com> Date: Fri, 16 Feb 2024 22:19:01 +0000 Subject: [PATCH] Add auto selection support. Move entry selection logic to flow Engine to be shared between screens. demo: http://shortn/_GYz5GifqrK BUG: 324276451 Test: Manual. Change-Id: I96edee71df1dc0cf5fb52f2bcb090230beb5bf83 --- .../credentialmanager/ktx/CredentialKtx.kt | 3 ++- .../CredentialSelectorViewModel.kt | 25 +++++++++++++++++++ .../android/credentialmanager/FlowEngine.kt | 12 +++++++++ .../android/credentialmanager/ui/WearApp.kt | 8 ++++++ .../single/password/SinglePasswordScreen.kt | 17 ++----------- 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt index a5f227a3adc9..f1d31ba9fbc1 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt @@ -46,8 +46,9 @@ import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.model.get.ProviderInfo import com.android.credentialmanager.model.get.RemoteEntryInfo import com.android.credentialmanager.TAG +import com.android.credentialmanager.model.EntryInfo -fun CredentialEntryInfo.getIntentSenderRequest( +fun EntryInfo.getIntentSenderRequest( isAutoSelected: Boolean = false ): IntentSenderRequest? { val entryIntent = fillInIntent?.putExtra(IS_AUTO_SELECTED_KEY, isAutoSelected) diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index 88bdc3339db4..880038553d63 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -28,10 +28,14 @@ import com.android.credentialmanager.model.get.ActionEntryInfo import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.ui.mappers.toGet import android.util.Log +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.compose.runtime.Composable import com.android.credentialmanager.CredentialSelectorUiState.Cancel import com.android.credentialmanager.CredentialSelectorUiState.Close import com.android.credentialmanager.CredentialSelectorUiState.Create import com.android.credentialmanager.CredentialSelectorUiState.Idle +import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract +import com.android.credentialmanager.ktx.getIntentSenderRequest import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -46,6 +50,8 @@ class CredentialSelectorViewModel @Inject constructor( ) : FlowEngine, ViewModel() { private val isPrimaryScreen = MutableStateFlow(true) private val shouldClose = MutableStateFlow(false) + private lateinit var selectedEntry: EntryInfo + private var isAutoSelected: Boolean = false val uiState: StateFlow<CredentialSelectorUiState> = combine( credentialManagerClient.requests, @@ -107,6 +113,25 @@ class CredentialSelectorViewModel @Inject constructor( ) shouldClose.value = result } + + @Composable + override fun getEntrySelector(): (entry: EntryInfo, isAutoSelected: Boolean) -> Unit { + val launcher = rememberLauncherForActivityResult( + StartBalIntentSenderForResultContract() + ) { + sendSelectionResult(entryInfo = selectedEntry, + resultCode = it.resultCode, + resultData = it.data, + isAutoSelected = isAutoSelected) + } + return { selected, autoSelect -> + selectedEntry = selected + isAutoSelected = autoSelect + selected.getIntentSenderRequest()?.let { + launcher.launch(it) + } ?: Log.w(TAG, "Cannot parse IntentSenderRequest") + } + } } sealed class CredentialSelectorUiState { diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt index e4216446772b..2e80a7c672f4 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt @@ -17,6 +17,8 @@ package com.android.credentialmanager import android.content.Intent +import androidx.activity.result.IntentSenderRequest +import androidx.compose.runtime.Composable import com.android.credentialmanager.model.EntryInfo /** Engine of the credential selecting flow. */ @@ -42,4 +44,14 @@ interface FlowEngine { resultData: Intent? = null, isAutoSelected: Boolean = false, ) + + /** + * Helper function to get an entry selector. + * + * @return selector fun consumes selected [EntryInfo]. Once invoked, [IntentSenderRequest] would + * be launched and invocation of [sendSelectionResult] would happen right after launching result + * coming back. + */ + @Composable + fun getEntrySelector(): (entry: EntryInfo, isAutoSelected: Boolean) -> Unit } \ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt index f9a5887158eb..405de1d3acc6 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt @@ -43,6 +43,7 @@ import com.google.android.horologist.compose.navscaffold.WearNavScaffold import com.google.android.horologist.compose.navscaffold.composable import com.google.android.horologist.compose.navscaffold.scrollable import com.android.credentialmanager.model.CredentialType +import com.android.credentialmanager.model.EntryInfo import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFoldScreen @OptIn(ExperimentalHorologistApi::class) @@ -56,6 +57,7 @@ fun WearApp( val swipeToDismissBoxState = rememberSwipeToDismissBoxState() val navHostState = rememberSwipeDismissableNavHostState(swipeToDismissBoxState = swipeToDismissBoxState) + val selectEntry = flowEngine.getEntrySelector() val uiState by viewModel.uiState.collectAsStateWithLifecycle() WearNavScaffold( @@ -111,6 +113,7 @@ fun WearApp( navController = navController, state = state, onCloseApp = onCloseApp, + selectEntry = selectEntry ) } @@ -133,9 +136,14 @@ private fun handleGetNavigation( navController: NavController, state: CredentialSelectorUiState.Get, onCloseApp: () -> Unit, + selectEntry: (entry: EntryInfo, isAutoSelected: Boolean) -> Unit, ) { when (state) { is SingleEntry -> { + if (state.entry.isAutoSelectable) { + selectEntry(state.entry, true) + return + } when (state.entry.credentialType) { CredentialType.UNKNOWN -> { navController.navigateToSignInWithProviderScreen() diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt index 1f1a296dca9b..2ca8ef13c0cf 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt @@ -18,8 +18,6 @@ package com.android.credentialmanager.ui.screens.single.password -import android.util.Log -import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable @@ -28,9 +26,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.android.credentialmanager.FlowEngine import com.android.credentialmanager.R -import com.android.credentialmanager.TAG -import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract -import com.android.credentialmanager.ktx.getIntentSenderRequest import com.android.credentialmanager.ui.components.PasswordRow import com.android.credentialmanager.ui.components.ContinueChip import com.android.credentialmanager.ui.components.DismissChip @@ -57,11 +52,7 @@ fun SinglePasswordScreen( modifier: Modifier = Modifier, flowEngine: FlowEngine, ) { - val launcher = rememberLauncherForActivityResult( - StartBalIntentSenderForResultContract() - ) { - flowEngine.sendSelectionResult(entry, it.resultCode, it.data) - } + val selectEntry = flowEngine.getEntrySelector() SingleAccountScreen( headerContent = { SignInHeader( @@ -80,11 +71,7 @@ fun SinglePasswordScreen( ) { item { Column { - ContinueChip { - entry.getIntentSenderRequest()?.let { - launcher.launch(it) - } ?: Log.w(TAG, "Cannot parse IntentSenderRequest") - } + ContinueChip { selectEntry(entry, false) } SignInOptionsChip{ flowEngine.openSecondaryScreen() } DismissChip { flowEngine.cancel() } } -- GitLab