summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt3
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt56
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt45
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt20
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt78
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt77
6 files changed, 131 insertions, 148 deletions
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 0df40d7adba5..283dc7d6fe08 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -25,7 +25,6 @@ import androidx.wear.compose.material.MaterialTheme
import com.android.credentialmanager.ui.WearApp
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import dagger.hilt.android.AndroidEntryPoint
-import kotlin.system.exitProcess
@AndroidEntryPoint(ComponentActivity::class)
class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
@@ -40,7 +39,7 @@ class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
MaterialTheme {
WearApp(
viewModel = viewModel,
- onCloseApp = { exitProcess(0) },
+ onCloseApp = { finish() },
)
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 2fc98e27bea1..366e3a7970c7 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -19,11 +19,14 @@ package com.android.credentialmanager
import android.content.Intent
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.android.credentialmanager.CredentialSelectorUiState.Get
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.EntryInfo
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 dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -35,10 +38,20 @@ import javax.inject.Inject
@HiltViewModel
class CredentialSelectorViewModel @Inject constructor(
private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
- private val isPrimaryScreen = MutableStateFlow(false)
- val uiState: StateFlow<CredentialSelectorUiState> = credentialManagerClient.requests
- .combine(isPrimaryScreen) { request, isPrimary ->
+) : FlowEngine, ViewModel() {
+ private val isPrimaryScreen = MutableStateFlow(true)
+ private val shouldClose = MutableStateFlow(false)
+ val uiState: StateFlow<CredentialSelectorUiState> =
+ combine(
+ credentialManagerClient.requests,
+ isPrimaryScreen,
+ shouldClose
+ ) { request, isPrimary, shouldClose ->
+ if (shouldClose) {
+ Log.d(TAG, "Request finished, closing ")
+ return@combine CredentialSelectorUiState.Close
+ }
+
when (request) {
null -> CredentialSelectorUiState.Idle
is Request.Cancel -> CredentialSelectorUiState.Cancel(request.appName)
@@ -56,6 +69,41 @@ class CredentialSelectorViewModel @Inject constructor(
fun updateRequest(intent: Intent) {
credentialManagerClient.updateRequest(intent = intent)
}
+
+ override fun back() {
+ Log.d(TAG, "OnBackPressed")
+ when (uiState.value) {
+ is Get.MultipleEntry -> isPrimaryScreen.value = true
+ else -> {
+ shouldClose.value = true
+ // TODO("b/300422310 - [Wear] Implement UI for cancellation request with message")
+ }
+ }
+ }
+
+ override fun cancel() {
+ shouldClose.value = true
+ // TODO("b/300422310 - [Wear] Implement UI for cancellation request with message")
+ }
+
+ override fun openSecondaryScreen() {
+ isPrimaryScreen.value = false
+ }
+
+ override fun sendSelectionResult(
+ entryInfo: EntryInfo,
+ resultCode: Int?,
+ resultData: Intent?,
+ isAutoSelected: Boolean,
+ ) {
+ val result = credentialManagerClient.sendEntrySelectionResult(
+ entryInfo = entryInfo,
+ resultCode = resultCode,
+ resultData = resultData,
+ isAutoSelected = isAutoSelected
+ )
+ shouldClose.value = result
+ }
}
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
new file mode 100644
index 000000000000..e4216446772b
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 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.Intent
+import com.android.credentialmanager.model.EntryInfo
+
+/** Engine of the credential selecting flow. */
+interface FlowEngine {
+ /** Back from previous stage. */
+ fun back()
+ /** Cancels the selection flow. */
+ fun cancel()
+ /** Opens secondary screen. */
+ fun openSecondaryScreen()
+ /**
+ * Sends [entryInfo] as long as result after launching [EntryInfo.pendingIntent] with
+ * [EntryInfo.fillInIntent].
+ *
+ * @param entryInfo: selected entry.
+ * @param resultCode: result code received after launch.
+ * @param resultData: data received after launch
+ * @param isAutoSelected: whether the entry is auto selected or by user.
+ */
+ fun sendSelectionResult(
+ entryInfo: EntryInfo,
+ resultCode: Int? = null,
+ resultData: Intent? = null,
+ isAutoSelected: Boolean = false,
+ )
+} \ 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 f7158e89a5cd..332b81634df7 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -18,8 +18,11 @@
package com.android.credentialmanager.ui
+import android.util.Log
+import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
@@ -29,6 +32,8 @@ import com.android.credentialmanager.CredentialSelectorUiState
import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
import com.android.credentialmanager.CredentialSelectorViewModel
+import com.android.credentialmanager.FlowEngine
+import com.android.credentialmanager.TAG
import com.android.credentialmanager.ui.screens.LoadingScreen
import com.android.credentialmanager.ui.screens.single.passkey.SinglePasskeyScreen
import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
@@ -44,6 +49,7 @@ import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFoldScr
@Composable
fun WearApp(
viewModel: CredentialSelectorViewModel,
+ flowEngine: FlowEngine = viewModel,
onCloseApp: () -> Unit,
) {
val navController = rememberSwipeDismissableNavController()
@@ -52,7 +58,6 @@ fun WearApp(
rememberSwipeDismissableNavHostState(swipeToDismissBoxState = swipeToDismissBoxState)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
WearNavScaffold(
startDestination = Screen.Loading.route,
navController = navController,
@@ -61,11 +66,11 @@ fun WearApp(
composable(Screen.Loading.route) {
LoadingScreen()
}
-
scrollable(Screen.SinglePasswordScreen.route) {
SinglePasswordScreen(
- credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+ entry = (remember { uiState } as SingleEntry).entry,
columnState = it.columnState,
+ flowEngine = flowEngine,
)
}
@@ -88,10 +93,13 @@ fun WearApp(
credentialSelectorUiState = viewModel.uiState.value as MultipleEntry,
screenIcon = null,
columnState = it.columnState,
- )
+ )
}
}
-
+ BackHandler(true) {
+ viewModel.back()
+ }
+ Log.d(TAG, "uiState change, state: $uiState")
when (val state = uiState) {
CredentialSelectorUiState.Idle -> {
if (navController.currentDestination?.route != Screen.Loading.route) {
@@ -142,7 +150,7 @@ private fun handleGetNavigation(
}
}
- is CredentialSelectorUiState.Get.MultipleEntry -> {
+ is MultipleEntry -> {
navController.navigateToMultipleCredentialsFoldScreen()
}
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 4c7f583fcb09..1f1a296dca9b 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,22 +18,19 @@
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
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+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
@@ -41,71 +38,30 @@ import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.single.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
/**
* Screen that shows sign in with provider credential.
*
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry.
* @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
* @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasswordScreen(
- credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
- columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
-) {
- viewModel.initialize(credentialSelectorUiState.entry)
-
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (val state = uiState) {
- UiState.CredentialScreen -> {
- SinglePasswordScreen(
- credentialSelectorUiState.entry,
- columnState,
- modifier,
- viewModel
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onPasswordInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- state.intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- // TODO(b/322797032) add valid navigation path here for going back
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-private fun SinglePasswordScreen(
entry: CredentialEntryInfo,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
- viewModel: SinglePasswordScreenViewModel,
+ flowEngine: FlowEngine,
) {
+ val launcher = rememberLauncherForActivityResult(
+ StartBalIntentSenderForResultContract()
+ ) {
+ flowEngine.sendSelectionResult(entry, it.resultCode, it.data)
+ }
SingleAccountScreen(
headerContent = {
SignInHeader(
@@ -124,9 +80,13 @@ private fun SinglePasswordScreen(
) {
item {
Column {
- ContinueChip(viewModel::onContinueClick)
- SignInOptionsChip(viewModel::onSignInOptionsClick)
- DismissChip(viewModel::onDismissClick)
+ ContinueChip {
+ entry.getIntentSenderRequest()?.let {
+ launcher.launch(it)
+ } ?: Log.w(TAG, "Cannot parse IntentSenderRequest")
+ }
+ SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+ DismissChip { flowEngine.cancel() }
}
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
deleted file mode 100644
index 8debecbac599..000000000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2024 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.0N
- *
- * 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.ui.screens.single.password
-
-import android.content.Intent
-import android.credentials.selection.UserSelectionDialogResult
-import android.credentials.selection.ProviderPendingIntentResponse
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.single.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-@HiltViewModel
-class SinglePasswordScreenViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- @MainThread
- fun initialize(entryInfo: CredentialEntryInfo) {
- this.entryInfo = entryInfo
- }
-
- fun onDismissClick() {
- _uiState.value = UiState.Cancel
- }
-
- fun onContinueClick() {
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onSignInOptionsClick() {
- }
-
- fun onPasswordInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-}