Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c9a80cdb authored by Helen Qin's avatar Helen Qin
Browse files

Fix the duplicate intent launching issue.

This fixed unintended duplicate intent launching issue for
1. action chips for get flow
2. settings for create flow.
Also improved the data model to better fit our practical purposes.

Bug: 264943119
Fix: 264943119
Test: local deployment
Change-Id: I57be60060012465655306da83e8c2972b37c0b8b
parent cfcf9f65
Loading
Loading
Loading
Loading
+61 −66
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.credentialmanager

import android.content.Intent
import android.credentials.ui.RequestInfo
import android.os.Bundle
import android.provider.Settings
import android.util.Log
@@ -26,20 +27,17 @@ import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.credentialmanager.common.DialogType
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.DialogState
import com.android.credentialmanager.common.ProviderActivityResult
import com.android.credentialmanager.common.ResultState
import com.android.credentialmanager.createflow.CreateCredentialScreen
import com.android.credentialmanager.createflow.CreateCredentialViewModel
import com.android.credentialmanager.getflow.GetCredentialScreen
import com.android.credentialmanager.getflow.GetCredentialViewModel
import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
import kotlinx.coroutines.launch

@ExperimentalMaterialApi
class CredentialSelectorActivity : ComponentActivity() {
@@ -50,29 +48,27 @@ class CredentialSelectorActivity : ComponentActivity() {
        val requestInfo = credManRepo.requestInfo
        setContent {
            CredentialSelectorTheme {
        CredentialManagerBottomSheet(DialogType.toDialogType(requestInfo.type), credManRepo)
                CredentialManagerBottomSheet(requestInfo.type, credManRepo)
            }
        }
    }

    @ExperimentalMaterialApi
    @Composable
  fun CredentialManagerBottomSheet(dialogType: DialogType, credManRepo: CredentialManagerRepo) {
    fun CredentialManagerBottomSheet(requestType: String, credManRepo: CredentialManagerRepo) {
        val providerActivityResult = remember { mutableStateOf<ProviderActivityResult?>(null) }
        val launcher = rememberLauncherForActivityResult(
            ActivityResultContracts.StartIntentSenderForResult()
        ) {
            providerActivityResult.value = ProviderActivityResult(it.resultCode, it.data)
        }
    when (dialogType) {
      DialogType.CREATE_PASSKEY -> {
        when (requestType) {
            RequestInfo.TYPE_CREATE -> {
                val viewModel: CreateCredentialViewModel = viewModel {
                    CreateCredentialViewModel(credManRepo)
                }
        lifecycleScope.launch {
          viewModel.observeDialogResult().collect{ dialogResult ->
            onCancel(dialogResult)
          }
                LaunchedEffect(viewModel.uiState.dialogState) {
                    handleDialogState(viewModel.uiState.dialogState)
                }
                providerActivityResult.value?.let {
                    viewModel.onProviderActivityResult(it)
@@ -80,14 +76,12 @@ class CredentialSelectorActivity : ComponentActivity() {
                }
                CreateCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
            }
      DialogType.GET_CREDENTIALS -> {
            RequestInfo.TYPE_GET -> {
                val viewModel: GetCredentialViewModel = viewModel {
                    GetCredentialViewModel(credManRepo)
                }
        lifecycleScope.launch {
          viewModel.observeDialogResult().collect{ dialogResult ->
            onCancel(dialogResult)
          }
                LaunchedEffect(viewModel.uiState.dialogState) {
                    handleDialogState(viewModel.uiState.dialogState)
                }
                providerActivityResult.value?.let {
                    viewModel.onProviderActivityResult(it)
@@ -102,11 +96,12 @@ class CredentialSelectorActivity : ComponentActivity() {
        }
    }

  private fun onCancel(dialogResut: DialogResult) {
    if (dialogResut.resultState == ResultState
        .COMPLETE || dialogResut.resultState == ResultState.NORMAL_CANCELED) {
    private fun handleDialogState(dialogState: DialogState) {
        if (dialogState == DialogState.COMPLETE) {
            Log.i("AccountSelector", "Received signal to finish the activity.")
            this@CredentialSelectorActivity.finish()
    } else if (dialogResut.resultState == ResultState.LAUNCH_SETTING_CANCELED) {
        } else if (dialogState == DialogState.CANCELED_FOR_SETTINGS) {
            Log.i("AccountSelector", "Received signal to finish the activity and launch settings.")
            this@CredentialSelectorActivity.startActivity(Intent(Settings.ACTION_SYNC_SETTINGS))
            this@CredentialSelectorActivity.finish()
        }
+6 −0
Original line number Diff line number Diff line
@@ -16,6 +16,12 @@

package com.android.credentialmanager.common

enum class DialogState {
  ACTIVE,
  COMPLETE,
  CANCELED_FOR_SETTINGS,
}

enum class ResultState {
  COMPLETE,
  NORMAL_CANCELED,
+2 −3
Original line number Diff line number Diff line
@@ -19,16 +19,15 @@ package com.android.credentialmanager.common
import android.credentials.ui.RequestInfo

enum class DialogType {
  CREATE_PASSKEY,
  CREATE_CREDENTIAL,
  GET_CREDENTIALS,
  CREATE_PASSWORD,
  UNKNOWN;

  companion object {
    fun toDialogType(value: String): DialogType {
      return when (value) {
        RequestInfo.TYPE_GET -> GET_CREDENTIALS
        RequestInfo.TYPE_CREATE -> CREATE_PASSKEY
        RequestInfo.TYPE_CREATE -> CREATE_CREDENTIAL
        else -> UNKNOWN
      }
    }
+27 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.common

enum class ProviderActivityState {
    /** No provider activity is active nor is any ready for launch, */
    NOT_APPLICABLE,
    /** Ready to launch the provider activity. */
    READY_TO_LAUNCH,
    /** The provider activity is launched and we are waiting for its result. We should hide our UI
     *  content when this happens. */
    PENDING,
}
 No newline at end of file
+80 −57
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
@@ -66,7 +67,11 @@ fun CreateCredentialScreen(
        sheetState = state,
        sheetContent = {
            val uiState = viewModel.uiState
            if (!uiState.hidden) {
            // Hide the sheet content as opposed to the whole bottom sheet to maintain the scrim
            // background color even when the content should be hidden while waiting for
            // results from the provider app.
            when (uiState.providerActivityState) {
                ProviderActivityState.NOT_APPLICABLE -> {
                    when (uiState.currentScreenState) {
                        CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
                            onConfirm = viewModel::onConfirmIntro,
@@ -78,17 +83,21 @@ fun CreateCredentialScreen(
                            disabledProviderList = uiState.disabledProviders,
                            sortedCreateOptionsPairs = uiState.sortedCreateOptionsPairs,
                            onOptionSelected = viewModel::onEntrySelectedFromFirstUseScreen,
                        onDisabledProvidersSelected = viewModel::onDisabledProvidersSelected,
                        onMoreOptionsSelected = viewModel::onMoreOptionsSelectedOnProviderSelection,
                            onDisabledProvidersSelected =
                            viewModel::onDisabledProvidersSelected,
                            onMoreOptionsSelected =
                            viewModel::onMoreOptionsSelectedOnProviderSelection,
                        )
                        CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
                            requestDisplayInfo = uiState.requestDisplayInfo,
                            enabledProviderList = uiState.enabledProviders,
                            providerInfo = uiState.activeEntry?.activeProvider!!,
                        createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
                            createOptionInfo =
                            uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
                            onOptionSelected = viewModel::onEntrySelected,
                            onConfirm = viewModel::onConfirmEntrySelected,
                        onMoreOptionsSelected = viewModel::onMoreOptionsSelectedOnCreationSelection,
                            onMoreOptionsSelected =
                            viewModel::onMoreOptionsSelectedOnCreationSelection,
                        )
                        CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
                            requestDisplayInfo = uiState.requestDisplayInfo,
@@ -101,8 +110,10 @@ fun CreateCredentialScreen(
                            viewModel::onBackProviderSelectionButtonSelected,
                            onBackCreationSelectionButtonSelected =
                            viewModel::onBackCreationSelectionButtonSelected,
                        onOptionSelected = viewModel::onEntrySelectedFromMoreOptionScreen,
                        onDisabledProvidersSelected = viewModel::onDisabledProvidersSelected,
                            onOptionSelected =
                            viewModel::onEntrySelectedFromMoreOptionScreen,
                            onDisabledProvidersSelected =
                            viewModel::onDisabledProvidersSelected,
                            onRemoteEntrySelected = viewModel::onEntrySelected,
                        )
                        CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
@@ -116,14 +127,24 @@ fun CreateCredentialScreen(
                            onOptionSelected = viewModel::onEntrySelected,
                            onConfirm = viewModel::onConfirmEntrySelected,
                        )
                    CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> MoreAboutPasskeysIntroCard(
                        CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO ->
                            MoreAboutPasskeysIntroCard(
                                onBackPasskeyIntroButtonSelected =
                                viewModel::onBackPasskeyIntroButtonSelected,
                            )
                    }
            } else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
                }
                ProviderActivityState.READY_TO_LAUNCH -> {
                    // Launch only once per providerActivityState change so that the provider
                    // UI will not be accidentally launched twice.
                    LaunchedEffect(uiState.providerActivityState) {
                        viewModel.launchProviderUi(providerActivityLauncher)
                    }
                }
                ProviderActivityState.PENDING -> {
                    // Hide our content when the provider activity is active.
                }
            }
        },
        scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
        sheetShape = EntryShape.TopRoundedCorner,
@@ -986,7 +1007,9 @@ fun MoreOptionsDisabledProvidersRow(
                    )
                    // TODO: Update the subtitle once design is confirmed
                    TextSecondary(
                        text = disabledProviders.joinToString(separator = " • ") { it.displayName },
                        text = disabledProviders.joinToString(separator = " • ") {
                            it.displayName
                        },
                        style = MaterialTheme.typography.bodyMedium,
                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
                    )
Loading