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

Commit 57c45eeb authored by Helen Qin's avatar Helen Qin
Browse files

Use service info instead of app info and revamped data parsing

App info was introduced as a temporary solution for testing. It will be
offically removed soon. With this change, the UI will pick up actual
service info for non-testing queries.

Also revamped input data parsing and make sure the UI does not crashes
but instead propagates errors and exists gracefully upon parsing
failures.

Will do another pass to make sure the whole E2E can handle errors
gracefully and add anything missing as a follow up.

Bug: 264898201
Bug: 264906200
Fix: 264898201
Test: local deployment
Change-Id: I2ab3b0e7908171911399cacc6ebcd489997d4e69
parent fc16dc4b
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -56,14 +56,14 @@ public class BaseDialogResult implements Parcelable {
     * The intent extra key for the {@code BaseDialogResult} object when the credential
     * selector activity finishes.
     */
    private static final String EXTRA_BASE_RESULT =
            "android.credentials.ui.extra.BASE_RESULT";
    private static final String EXTRA_BASE_RESULT = "android.credentials.ui.extra.BASE_RESULT";

    /** @hide **/
    @IntDef(prefix = {"RESULT_CODE_"}, value = {
            RESULT_CODE_DIALOG_USER_CANCELED,
            RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS,
            RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
            RESULT_CODE_DATA_PARSING_FAILURE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ResultCode {}
@@ -80,6 +80,10 @@ public class BaseDialogResult implements Parcelable {
     * {@code resultData}.
     */
    public static final int RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION = 2;
    /**
     * The UI was canceled because it failed to parse the incoming data.
     */
    public static final int RESULT_CODE_DATA_PARSING_FAILURE = 3;

    @NonNull
    private final IBinder mRequestToken;
+409 −374
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ class CredentialManagerRepo(
    val requestInfo: RequestInfo
    private val providerEnabledList: List<ProviderData>
    private val providerDisabledList: List<DisabledProviderData>?

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

@@ -109,7 +110,11 @@ class CredentialManagerRepo(
        onCancel(BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION)
    }

  private fun onCancel(cancelCode: Int) {
    fun onParsingFailureCancel() {
        onCancel(BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE)
    }

    fun onCancel(cancelCode: Int) {
        val resultData = Bundle()
        BaseDialogResult.addToBundle(BaseDialogResult(requestInfo.token), resultData)
        resultReceiver?.send(cancelCode, resultData)
@@ -131,34 +136,40 @@ class CredentialManagerRepo(
        )
        val resultData = Bundle()
        UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultData)
    resultReceiver?.send(BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION, resultData)
        resultReceiver?.send(
            BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
            resultData
        )
    }

  fun getCredentialInitialUiState(): GetCredentialUiState {
    fun getCredentialInitialUiState(): GetCredentialUiState? {
        val providerEnabledList = GetFlowUtils.toProviderList(
            // TODO: handle runtime cast error
      providerEnabledList as List<GetCredentialProviderData>, context)
            providerEnabledList as List<GetCredentialProviderData>, context
        )
        val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo, context)
        return GetCredentialUiState(
            providerEnabledList,
      requestDisplayInfo,
            requestDisplayInfo ?: return null,
        )
    }

    fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
        val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
            // Handle runtime cast error
      providerEnabledList as List<CreateCredentialProviderData>, context)
            providerEnabledList as List<CreateCredentialProviderData>, context
        )
        return providerEnabledList
    }

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

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

@@ -169,12 +180,16 @@ class CredentialManagerRepo(
                .Builder("io.enpass.app")
                .setSaveEntries(
                    listOf<Entry>(
                      newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
                        newCreateEntry(
                            "key1", "subkey-1", "elisa.beckett@gmail.com",
                            20, 7, 27, 10L,
                          "Optional footer description"),
                      newCreateEntry("key1", "subkey-2", "elisa.work@google.com",
                            "Optional footer description"
                        ),
                        newCreateEntry(
                            "key1", "subkey-2", "elisa.work@google.com",
                            20, 7, 27, 12L,
                      null),
                            null
                        ),
                    )
                )
                .setRemoteEntry(
@@ -185,12 +200,16 @@ class CredentialManagerRepo(
                .Builder("com.dashlane")
                .setSaveEntries(
                    listOf<Entry>(
                      newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
                        newCreateEntry(
                            "key1", "subkey-3", "elisa.beckett@dashlane.com",
                            20, 7, 27, 11L,
                          null),
                      newCreateEntry("key1", "subkey-4", "elisa.work@dashlane.com",
                            null
                        ),
                        newCreateEntry(
                            "key1", "subkey-4", "elisa.work@dashlane.com",
                            20, 7, 27, 14L,
                          null),
                            null
                        ),
                    )
                )
                .build(),
@@ -311,13 +330,17 @@ class CredentialManagerRepo(
            .setPackage("com.androidauth.androidvault")
        intent.putExtra("provider_extra_sample", "testprovider")

        val pendingIntent = PendingIntent.getActivity(context, 1,
        val pendingIntent = PendingIntent.getActivity(
            context, 1,
            intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
                or PendingIntent.FLAG_ONE_SHOT))
                or PendingIntent.FLAG_ONE_SHOT)
        )

        val credentialEntry = CredentialEntry(credentialType, credentialTypeDisplayName, userName,
        val credentialEntry = CredentialEntry(
            credentialType, credentialTypeDisplayName, userName,
            userDisplayName, pendingIntent, lastUsedTimeMillis
                ?: 0L, null, false)
                ?: 0L, null, false
        )

        return Entry(
            key,
@@ -340,18 +363,22 @@ class CredentialManagerRepo(
        val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
            .setPackage("com.androidauth.androidvault")
        intent.putExtra("provider_extra_sample", "testprovider")
        val pendingIntent = PendingIntent.getActivity(context, 1,
        val pendingIntent = PendingIntent.getActivity(
            context, 1,
            intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
                or PendingIntent.FLAG_ONE_SHOT))
                or PendingIntent.FLAG_ONE_SHOT)
        )
        val createPasswordRequest = android.service.credentials.CreateCredentialRequest(
            android.service.credentials.CallingAppInfo(
                        context.applicationInfo.packageName, SigningInfo()),
                context.applicationInfo.packageName, SigningInfo()
            ),
            TYPE_PASSWORD_CREDENTIAL,
            toCredentialDataBundle("beckett-bakert@gmail.com", "password123")
        )
        val fillInIntent = Intent().putExtra(
            CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
                createPasswordRequest)
            createPasswordRequest
        )

        val createEntry = CreateEntry(
            providerDisplayName, pendingIntent,
@@ -359,7 +386,8 @@ class CredentialManagerRepo(
            listOf(
                CredentialCountInformation.createPasswordCountInformation(passwordCount),
                CredentialCountInformation.createPublicKeyCountInformation(passkeyCount),
                ), footerDescription)
            ), footerDescription
        )
        return Entry(
            key,
            subkey,
@@ -382,11 +410,13 @@ class CredentialManagerRepo(
    }

    private fun testCreatePasskeyRequestInfo(): RequestInfo {
    val request = CreatePublicKeyCredentialRequest("{\"extensions\": {\n" +
        val request = CreatePublicKeyCredentialRequest(
            "{\"extensions\": {\n" +
                "                     \"webauthn.loc\": true\n" +
                "                   },\n" +
                "                   \"attestation\": \"direct\",\n" +
            "                   \"challenge\": \"-rSQHXSQUdaK1N-La5bE-JPt6EVAW4SxX1K_tXhZ_Gk\",\n" +
                "                   \"challenge\":" +
                " \"-rSQHXSQUdaK1N-La5bE-JPt6EVAW4SxX1K_tXhZ_Gk\",\n" +
                "                   \"user\": {\n" +
                "                     \"displayName\": \"testName\",\n" +
                "                     \"name\": \"credManTesting@gmail.com\",\n" +
@@ -415,7 +445,8 @@ class CredentialManagerRepo(
                "                   \"authenticatorSelection\": {\n" +
                "                     \"residentKey\": \"required\",\n" +
                "                     \"requireResidentKey\": true\n" +
            "                   }}")
                "                   }}"
        )
        val credentialData = request.credentialData
        return RequestInfo.newCreateRequestInfo(
            Binder(),
@@ -467,7 +498,11 @@ class CredentialManagerRepo(
            )
                .addGetCredentialOption(
                    GetCredentialOption(
            TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), Bundle(), /*isSystemProviderRequired=*/ false)
                        TYPE_PUBLIC_KEY_CREDENTIAL,
                        Bundle(),
                        Bundle(), /*isSystemProviderRequired=*/
                        false
                    )
                )
                .build(),
            "com.google.android.youtube"
+47 −11
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ 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
@@ -45,12 +46,16 @@ class CredentialSelectorActivity : ComponentActivity() {
        super.onCreate(savedInstanceState)
        val credManRepo = CredentialManagerRepo(this, intent)
        UserConfigRepo.setup(this)
        val requestInfo = credManRepo.requestInfo
        try {
            setContent {
                CredentialSelectorTheme {
                CredentialManagerBottomSheet(requestInfo.type, credManRepo)
                    CredentialManagerBottomSheet(credManRepo.requestInfo.type, credManRepo)
                }
            }
        } catch (e: Exception) {
            Log.e(Constants.LOG_TAG, "Failed to show the credential selector", e)
            reportInstantiationErrorAndFinishActivity(credManRepo)
        }
    }

    @ExperimentalMaterialApi
@@ -65,7 +70,22 @@ class CredentialSelectorActivity : ComponentActivity() {
        when (requestType) {
            RequestInfo.TYPE_CREATE -> {
                val viewModel: CreateCredentialViewModel = viewModel {
                    CreateCredentialViewModel(credManRepo)
                    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
                    }
                }
                LaunchedEffect(viewModel.uiState.dialogState) {
                    handleDialogState(viewModel.uiState.dialogState)
@@ -74,11 +94,21 @@ class CredentialSelectorActivity : ComponentActivity() {
                    viewModel.onProviderActivityResult(it)
                    providerActivityResult.value = null
                }
                CreateCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
                CreateCredentialScreen(
                    viewModel = viewModel,
                    providerActivityLauncher = launcher
                )
            }
            RequestInfo.TYPE_GET -> {
                val viewModel: GetCredentialViewModel = viewModel {
                    GetCredentialViewModel(credManRepo)
                    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)
@@ -90,18 +120,24 @@ class CredentialSelectorActivity : ComponentActivity() {
                GetCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
            }
            else -> {
                Log.w("AccountSelector", "Unknown type, not rendering any UI")
                this.finish()
                Log.d(Constants.LOG_TAG, "Unknown type, not rendering any UI")
                reportInstantiationErrorAndFinishActivity(credManRepo)
            }
        }
    }

    private fun reportInstantiationErrorAndFinishActivity(credManRepo: CredentialManagerRepo) {
        Log.w(Constants.LOG_TAG, "Finishing the activity due to instantiation failure.")
        credManRepo.onParsingFailureCancel()
        this@CredentialSelectorActivity.finish()
    }

    private fun handleDialogState(dialogState: DialogState) {
        if (dialogState == DialogState.COMPLETE) {
            Log.i("AccountSelector", "Received signal to finish the activity.")
            Log.d(Constants.LOG_TAG, "Received signal to finish the activity.")
            this@CredentialSelectorActivity.finish()
        } else if (dialogState == DialogState.CANCELED_FOR_SETTINGS) {
            Log.i("AccountSelector", "Received signal to finish the activity and launch settings.")
            Log.d(Constants.LOG_TAG, "Received signal to finish the activity and launch settings.")
            this@CredentialSelectorActivity.startActivity(Intent(Settings.ACTION_SYNC_SETTINGS))
            this@CredentialSelectorActivity.finish()
        }
+411 −345

File changed.

Preview size limit exceeded, changes collapsed.

+23 −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

class Constants {
    companion object Constants {
        const val LOG_TAG = "CredentialSelector"
    }
}
 No newline at end of file
Loading