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

Commit 42fda5d4 authored by Helen Qin's avatar Helen Qin Committed by Android (Google) Code Review
Browse files

Merge "Support onNewIntent()."

parents 412bfbe2 ccb6f37c
Loading
Loading
Loading
Loading
+55 −11
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.credentials.ui.BaseDialogResult
import android.credentials.ui.ProviderPendingIntentResponse
import android.credentials.ui.UserSelectionDialogResult
import android.net.Uri
import android.os.IBinder
import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
@@ -52,14 +53,16 @@ import java.time.Instant
class CredentialManagerRepo(
    private val context: Context,
    intent: Intent,
    userConfigRepo: UserConfigRepo,
) {
    val requestInfo: RequestInfo
    private val providerEnabledList: List<ProviderData>
    private val providerDisabledList: List<DisabledProviderData>?

    // TODO: require non-null.
    val resultReceiver: ResultReceiver?

    var initialUiState: UiState

    init {
        requestInfo = intent.extras?.getParcelable(
            RequestInfo.EXTRA_REQUEST_INFO,
@@ -93,6 +96,35 @@ class CredentialManagerRepo(
            Constants.EXTRA_RESULT_RECEIVER,
            ResultReceiver::class.java
        )

        initialUiState = when (requestInfo.type) {
            RequestInfo.TYPE_CREATE -> {
                val defaultProviderId = userConfigRepo.getDefaultProviderId()
                val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
                val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
                val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
                val requestDisplayInfoUiState = getCreateRequestDisplayInfoInitialUiState()!!
                UiState(
                    createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
                        providerEnableListUiState,
                        providerDisableListUiState,
                        defaultProviderId,
                        requestDisplayInfoUiState,
                        /** isOnPasskeyIntroStateAlready = */ false,
                        isPasskeyFirstUse)!!,
                    getCredentialUiState = null,
                )
            }
            RequestInfo.TYPE_GET -> UiState(
                createCredentialUiState = null,
                getCredentialUiState = getCredentialInitialUiState()!!,
            )
            else -> throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
        }
    }

    fun initState(): UiState {
        return initialUiState
    }

    // The dialog is canceled by the user.
@@ -110,9 +142,7 @@ class CredentialManagerRepo(
    }

    fun onCancel(cancelCode: Int) {
        val resultData = Bundle()
        BaseDialogResult.addToBundle(BaseDialogResult(requestInfo.token), resultData)
        resultReceiver?.send(cancelCode, resultData)
        sendCancellationCode(cancelCode, requestInfo.token, resultReceiver)
    }

    fun onOptionSelected(
@@ -129,15 +159,15 @@ class CredentialManagerRepo(
            entrySubkey,
            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
        )
        val resultData = Bundle()
        UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultData)
        val resultDataBundle = Bundle()
        UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle)
        resultReceiver?.send(
            BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
            resultData
            resultDataBundle
        )
    }

    fun getCredentialInitialUiState(): GetCredentialUiState? {
    private fun getCredentialInitialUiState(): GetCredentialUiState? {
        val providerEnabledList = GetFlowUtils.toProviderList(
            // TODO: handle runtime cast error
            providerEnabledList as List<GetCredentialProviderData>, context
@@ -149,7 +179,7 @@ class CredentialManagerRepo(
        )
    }

    fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
    private fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
        val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
            // Handle runtime cast error
            providerEnabledList as List<CreateCredentialProviderData>, context
@@ -157,17 +187,31 @@ class CredentialManagerRepo(
        return providerEnabledList
    }

    fun getCreateProviderDisableListInitialUiState(): List<DisabledProviderInfo> {
    private fun getCreateProviderDisableListInitialUiState(): List<DisabledProviderInfo> {
        return CreateFlowUtils.toDisabledProviderList(
            // Handle runtime cast error
            providerDisabledList, context
        )
    }

    fun getCreateRequestDisplayInfoInitialUiState(): RequestDisplayInfo? {
    private fun getCreateRequestDisplayInfoInitialUiState(): RequestDisplayInfo? {
        return CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
    }

    companion object {
        fun sendCancellationCode(
            cancelCode: Int,
            requestToken: IBinder?,
            resultReceiver: ResultReceiver?
        ) {
            if (requestToken != null && resultReceiver != null) {
                val resultData = Bundle()
                BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
                resultReceiver.send(cancelCode, resultData)
            }
        }
    }

    // TODO: below are prototype functionalities. To be removed for productionization.
    private fun testCreateCredentialEnabledProviderList(): List<CreateCredentialProviderData> {
        return listOf(
+60 −68
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@
package com.android.credentialmanager

import android.content.Intent
import android.credentials.ui.BaseDialogResult
import android.credentials.ui.RequestInfo
import android.os.Bundle
import android.os.ResultReceiver
import android.provider.Settings
import android.util.Log
import androidx.activity.ComponentActivity
@@ -28,103 +30,76 @@ 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.viewmodel.compose.viewModel
import com.android.credentialmanager.common.Constants
import com.android.credentialmanager.common.DialogState
import com.android.credentialmanager.common.ProviderActivityResult
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

@ExperimentalMaterialApi
class CredentialSelectorActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val credManRepo = CredentialManagerRepo(this, intent)
        UserConfigRepo.setup(this)
        Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
        init(intent)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        Log.d(Constants.LOG_TAG, "Existing activity received new intent")
        init(intent)
    }

    fun init(intent: Intent) {
        try {
            val userConfigRepo = UserConfigRepo(this)
            val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
            setContent {
                CredentialSelectorTheme {
                    CredentialManagerBottomSheet(credManRepo.requestInfo.type, credManRepo)
                    CredentialManagerBottomSheet(
                        credManRepo,
                        userConfigRepo
                    )
                }
            }
        } catch (e: Exception) {
            Log.e(Constants.LOG_TAG, "Failed to show the credential selector", e)
            reportInstantiationErrorAndFinishActivity(credManRepo)
            onInitializationError(e, intent)
        }
    }

    @ExperimentalMaterialApi
    @Composable
    fun CredentialManagerBottomSheet(requestType: String, credManRepo: CredentialManagerRepo) {
        val providerActivityResult = remember { mutableStateOf<ProviderActivityResult?>(null) }
    fun CredentialManagerBottomSheet(
        credManRepo: CredentialManagerRepo,
        userConfigRepo: UserConfigRepo
    ) {
        val viewModel: CredentialSelectorViewModel = viewModel {
            CredentialSelectorViewModel(credManRepo, userConfigRepo)
        }
        val launcher = rememberLauncherForActivityResult(
            ActivityResultContracts.StartIntentSenderForResult()
        ) {
            providerActivityResult.value = ProviderActivityResult(it.resultCode, it.data)
        }
        when (requestType) {
            RequestInfo.TYPE_CREATE -> {
                val viewModel: CreateCredentialViewModel = viewModel {
                    val vm = CreateCredentialViewModel.newInstance(
                        credManRepo = credManRepo,
                        providerEnableListUiState =
                        credManRepo.getCreateProviderEnableListInitialUiState(),
                        providerDisableListUiState =
                        credManRepo.getCreateProviderDisableListInitialUiState(),
                        requestDisplayInfoUiState =
                        credManRepo.getCreateRequestDisplayInfoInitialUiState()
                    )
                    if (vm == null) {
                        // Input parsing failed. Close the activity.
                        reportInstantiationErrorAndFinishActivity(credManRepo)
                        throw IllegalStateException()
                    } else {
                        vm
                    }
            viewModel.onProviderActivityResult(ProviderActivityResult(it.resultCode, it.data))
        }
        LaunchedEffect(viewModel.uiState.dialogState) {
            handleDialogState(viewModel.uiState.dialogState)
        }
                providerActivityResult.value?.let {
                    viewModel.onProviderActivityResult(it)
                    providerActivityResult.value = null
                }

        if (viewModel.uiState.createCredentialUiState != null) {
            CreateCredentialScreen(
                viewModel = viewModel,
                providerActivityLauncher = launcher
            )
            }
            RequestInfo.TYPE_GET -> {
                val viewModel: GetCredentialViewModel = viewModel {
                    val initialUiState = credManRepo.getCredentialInitialUiState()
                    if (initialUiState == null) {
                        // Input parsing failed. Close the activity.
                        reportInstantiationErrorAndFinishActivity(credManRepo)
                        throw IllegalStateException()
                    } else {
                        GetCredentialViewModel(credManRepo, initialUiState)
                    }
                }
                LaunchedEffect(viewModel.uiState.dialogState) {
                    handleDialogState(viewModel.uiState.dialogState)
                }
                providerActivityResult.value?.let {
                    viewModel.onProviderActivityResult(it)
                    providerActivityResult.value = null
                }
        } else if (viewModel.uiState.getCredentialUiState != null) {
            GetCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
            }
            else -> {
                Log.d(Constants.LOG_TAG, "Unknown type, not rendering any UI")
        } else {
            Log.d(Constants.LOG_TAG, "UI wasn't able to render neither get nor create flow")
            reportInstantiationErrorAndFinishActivity(credManRepo)
        }
    }
    }

    private fun reportInstantiationErrorAndFinishActivity(credManRepo: CredentialManagerRepo) {
        Log.w(Constants.LOG_TAG, "Finishing the activity due to instantiation failure.")
@@ -142,4 +117,21 @@ class CredentialSelectorActivity : ComponentActivity() {
            this@CredentialSelectorActivity.finish()
        }
    }

    private fun onInitializationError(e: Exception, intent: Intent) {
        Log.e(Constants.LOG_TAG, "Failed to show the credential selector", e)
        val resultReceiver = intent.getParcelableExtra(
            android.credentials.ui.Constants.EXTRA_RESULT_RECEIVER,
            ResultReceiver::class.java
        )
        val requestInfo = intent.extras?.getParcelable(
            RequestInfo.EXTRA_REQUEST_INFO,
            RequestInfo::class.java
        )
        CredentialManagerRepo.sendCancellationCode(
            BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE,
            requestInfo?.token, resultReceiver
        )
        this.finish()
    }
}
+340 −0

File changed and moved.

Preview size limit exceeded, changes collapsed.

+20 −23
Original line number Diff line number Diff line
@@ -261,22 +261,22 @@ class GetFlowUtils {
            providerIcon: Drawable,
            authEntryList: List<Entry>,
        ): List<AuthenticationEntryInfo> {
            if (authEntryList.isEmpty()) {
                return listOf()
      }
            val authEntry = authEntryList[0]
            val result: MutableList<AuthenticationEntryInfo> = mutableListOf()
            authEntryList.forEach {
                val structuredAuthEntry =
                AuthenticationAction.fromSlice(authEntry.slice) ?: return listOf()
            return listOf(AuthenticationEntryInfo(
                    AuthenticationAction.fromSlice(it.slice) ?: return@forEach
                result.add(AuthenticationEntryInfo(
                    providerId = providerId,
                entryKey = authEntry.key,
                entrySubkey = authEntry.subkey,
                    entryKey = it.key,
                    entrySubkey = it.subkey,
                    pendingIntent = structuredAuthEntry.pendingIntent,
                fillInIntent = authEntry.frameworkExtrasIntent,
                    fillInIntent = it.frameworkExtrasIntent,
                    title = providerDisplayName,
                    icon = providerIcon,
                ))
            }
            return result
        }

        private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? {
            // TODO: should also call fromSlice after getting the official jetpack code.
@@ -459,10 +459,7 @@ class CreateFlowUtils {
                /*requestDisplayInfo=*/requestDisplayInfo,
                /*defaultProvider=*/defaultProvider, /*remoteEntry=*/remoteEntry,
                /*isPasskeyFirstUse=*/isPasskeyFirstUse
            )
            if (initialScreenState == null) {
                return null
            }
            ) ?: return null
            return CreateCredentialUiState(
                enabledProviders = enabledProviders,
                disabledProviders = disabledProviders,
+0 −12
Original line number Diff line number Diff line
@@ -50,21 +50,9 @@ class UserConfigRepo(context: Context) {
    }

    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
        }
    }
}
Loading