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

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

Merge "Launch provider pending intent from the CredentialManager UI."

parents c814f213 3e944957
Loading
Loading
Loading
Loading
+41 −9
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.credentialmanager

import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
import android.app.PendingIntent
import android.app.slice.Slice
import android.app.slice.SliceSpec
import android.content.Context
@@ -32,6 +33,7 @@ import android.credentials.ui.DisabledProviderData
import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
import android.credentials.ui.BaseDialogResult
import android.credentials.ui.ProviderPendingIntentResponse
import android.credentials.ui.UserSelectionDialogResult
import android.graphics.drawable.Icon
import android.os.Binder
@@ -54,7 +56,7 @@ class CredentialManagerRepo(
  private val context: Context,
  intent: Intent,
) {
  private val requestInfo: RequestInfo
  val requestInfo: RequestInfo
  private val providerEnabledList: List<ProviderData>
  private val providerDisabledList: List<DisabledProviderData>
  // TODO: require non-null.
@@ -75,7 +77,7 @@ class CredentialManagerRepo(
      RequestInfo.TYPE_GET ->
        intent.extras?.getParcelableArrayList(
          ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
          DisabledProviderData::class.java
          GetCredentialProviderData::class.java
        ) ?: testGetCredentialProviderList()
      else -> {
        // TODO: fail gracefully
@@ -101,12 +103,19 @@ class CredentialManagerRepo(
    resultReceiver?.send(BaseDialogResult.RESULT_CODE_DIALOG_CANCELED, resultData)
  }

  fun onOptionSelected(providerPackageName: String, entryKey: String, entrySubkey: String) {
  fun onOptionSelected(
    providerPackageName: String,
    entryKey: String,
    entrySubkey: String,
    resultCode: Int? = null,
    resultData: Intent? = null,
  ) {
    val userSelectionDialogResult = UserSelectionDialogResult(
      requestInfo.token,
      providerPackageName,
      entryKey,
      entrySubkey
      entrySubkey,
      if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
    )
    val resultData = Bundle()
    UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultData)
@@ -328,6 +337,14 @@ class CredentialManagerRepo(
    userDisplayName: String?,
    lastUsedTimeMillis: Long?,
  ): Entry {
    val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
      .setPackage("com.androidauth.androidvault")
    intent.putExtra("provider_extra_sample", "testprovider")

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

    val slice = Slice.Builder(
      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
    ).addText(
@@ -347,7 +364,9 @@ class CredentialManagerRepo(
    return Entry(
      key,
      subkey,
      slice.build()
      slice.build(),
      pendingIntent,
      null
    )
  }

@@ -360,10 +379,22 @@ class CredentialManagerRepo(
    totalCredentialCount: Int,
    lastUsedTimeMillis: Long,
  ): Entry {
    val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
      .setPackage("com.androidauth.androidvault")
    intent.putExtra("provider_extra_sample", "testprovider")
    val pendingIntent = PendingIntent.getActivity(context, 1,
      intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
              or PendingIntent.FLAG_ONE_SHOT))
    val createPasswordRequest = android.service.credentials.CreateCredentialRequest(
      context.applicationInfo.packageName,
      "PASSWORD",
      toBundle("beckett-bakert@gmail.com", "password123")
    )
    val fillInIntent = Intent().putExtra("create_request_params", createPasswordRequest)

    val slice = Slice.Builder(
      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
    )
      .addText(
    ).addText(
        providerDisplayName, null, listOf(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME))
      .addIcon(
        Icon.createWithResource(context, R.drawable.ic_passkey),
@@ -384,7 +415,9 @@ class CredentialManagerRepo(
    return Entry(
      key,
      subkey,
      slice
      slice,
      pendingIntent,
      fillInIntent,
    )
  }

@@ -415,7 +448,6 @@ class CredentialManagerRepo(
  }

  private fun testGetRequestInfo(): RequestInfo {
    val data = Bundle()
    return RequestInfo.newGetRequestInfo(
      Binder(),
      GetCredentialRequest.Builder()
+24 −19
Original line number Diff line number Diff line
@@ -16,17 +16,21 @@

package com.android.credentialmanager

import android.credentials.ui.RequestInfo
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
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.mutableStateOf
import androidx.compose.runtime.remember
import androidx.lifecycle.Observer
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.credentialmanager.common.DialogType
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ProviderActivityResult
import com.android.credentialmanager.common.ResultState
import com.android.credentialmanager.createflow.CreateCredentialScreen
import com.android.credentialmanager.createflow.CreateCredentialViewModel
@@ -39,28 +43,23 @@ class CredentialSelectorActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    CredentialManagerRepo.setup(this, intent)
    val requestInfo = intent.extras?.getParcelable<RequestInfo>(RequestInfo.EXTRA_REQUEST_INFO)
    if (requestInfo != null) {
      val requestType = requestInfo.type
    val requestInfo = CredentialManagerRepo.getInstance().requestInfo
    setContent {
      CredentialSelectorTheme {
          CredentialManagerBottomSheet(requestType)
        }
      }
    } else {
      // TODO: prototype only code to be removed. In production should exit.
      setContent {
        CredentialSelectorTheme {
          CredentialManagerBottomSheet(RequestInfo.TYPE_CREATE)
        }
        CredentialManagerBottomSheet(DialogType.toDialogType(requestInfo.type))
      }
    }
  }

  @ExperimentalMaterialApi
  @Composable
  fun CredentialManagerBottomSheet(operationType: String) {
    val dialogType = DialogType.toDialogType(operationType)
  fun CredentialManagerBottomSheet(dialogType: DialogType) {
    val providerActivityResult = remember { mutableStateOf<ProviderActivityResult?>(null) }
    val launcher = rememberLauncherForActivityResult(
      ActivityResultContracts.StartIntentSenderForResult()
    ) {
      providerActivityResult.value = ProviderActivityResult(it.resultCode, it.data)
    }
    when (dialogType) {
      DialogType.CREATE_PASSKEY -> {
        val viewModel: CreateCredentialViewModel = viewModel()
@@ -68,7 +67,10 @@ class CredentialSelectorActivity : ComponentActivity() {
          this@CredentialSelectorActivity,
          onCancel
        )
        CreateCredentialScreen(viewModel = viewModel)
        providerActivityResult.value?.let {
          viewModel.onProviderActivityResult(it)
        }
        CreateCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
      }
      DialogType.GET_CREDENTIALS -> {
        val viewModel: GetCredentialViewModel = viewModel()
@@ -76,7 +78,10 @@ class CredentialSelectorActivity : ComponentActivity() {
          this@CredentialSelectorActivity,
          onCancel
        )
        GetCredentialScreen(viewModel = viewModel)
        providerActivityResult.value?.let {
          viewModel.onProviderActivityResult(it)
        }
        GetCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
      }
      else -> {
        Log.w("AccountSelector", "Unknown type, not rendering any UI")
+20 −1
Original line number Diff line number Diff line
@@ -46,8 +46,15 @@ class GetFlowUtils {
      val packageManager = context.packageManager
      return providerDataList.map {
        // TODO: get from the actual service info
        val componentName = ComponentName.unflattenFromString(it.providerFlattenedComponentName)
        var packageName = componentName?.packageName
        if (componentName == null) {
          // TODO: Remove once test data is fixed
          packageName = it.providerFlattenedComponentName
        }

        val pkgInfo = packageManager
          .getPackageInfo(it.providerFlattenedComponentName,
          .getPackageInfo(packageName!!,
            PackageManager.PackageInfoFlags.of(0))
        val providerDisplayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString()
        // TODO: decide what to do when failed to load a provider icon
@@ -86,6 +93,8 @@ class GetFlowUtils {
          providerId = providerId,
          entryKey = it.key,
          entrySubkey = it.subkey,
          pendingIntent = it.pendingIntent,
          fillInIntent = it.frameworkExtrasIntent,
          credentialType = credentialEntryUi.credentialType.toString(),
          credentialTypeDisplayName = credentialEntryUi.credentialTypeDisplayName.toString(),
          userName = credentialEntryUi.userName.toString(),
@@ -113,6 +122,8 @@ class GetFlowUtils {
        providerId = providerId,
        entryKey = authEntry.key,
        entrySubkey = authEntry.subkey,
        pendingIntent = authEntry.pendingIntent,
        fillInIntent = authEntry.frameworkExtrasIntent,
        title = providerDisplayName,
        icon = providerIcon,
      )
@@ -127,6 +138,8 @@ class GetFlowUtils {
        providerId = providerId,
        entryKey = remoteEntry.key,
        entrySubkey = remoteEntry.subkey,
        pendingIntent = remoteEntry.pendingIntent,
        fillInIntent = remoteEntry.frameworkExtrasIntent,
      )
    }

@@ -142,6 +155,8 @@ class GetFlowUtils {
          providerId = providerId,
          entryKey = it.key,
          entrySubkey = it.subkey,
          pendingIntent = it.pendingIntent,
          fillInIntent = it.frameworkExtrasIntent,
          title = actionEntryUi.text.toString(),
          // TODO: gracefully fail
          icon = actionEntryUi.icon.loadDrawable(context)!!,
@@ -214,6 +229,8 @@ class CreateFlowUtils {
          // TODO: remove fallbacks
          entryKey = it.key,
          entrySubkey = it.subkey,
          pendingIntent = it.pendingIntent,
          fillInIntent = it.frameworkExtrasIntent,
          userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
          credentialTypeIcon = saveEntryUi.credentialTypeIcon?.loadDrawable(context)
            ?: context.getDrawable(R.drawable.ic_passkey)!!,
@@ -235,6 +252,8 @@ class CreateFlowUtils {
        RemoteInfo(
          entryKey = remoteEntry.key,
          entrySubkey = remoteEntry.subkey,
          pendingIntent = remoteEntry.pendingIntent,
          fillInIntent = remoteEntry.frameworkExtrasIntent,
        )
      } else null
    }
+24 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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

import android.content.Intent

data class ProviderActivityResult(
    val resultCode: Int,
    val data: Intent?,
)
 No newline at end of file
+9 −2
Original line number Diff line number Diff line
@@ -3,6 +3,9 @@
package com.android.credentialmanager.createflow

import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -50,7 +53,11 @@ import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Compa
@Composable
fun CreateCredentialScreen(
  viewModel: CreateCredentialViewModel,
  providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
  val primaryEntryCallback: () -> Unit = {
    viewModel.onPrimaryCreateOptionInfoSelected(providerActivityLauncher)
  }
  val state = rememberModalBottomSheetState(
    initialValue = ModalBottomSheetValue.Expanded,
    skipHalfExpanded = true
@@ -73,8 +80,8 @@ fun CreateCredentialScreen(
          requestDisplayInfo = uiState.requestDisplayInfo,
          providerInfo = uiState.activeEntry?.activeProvider!!,
          createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
          onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
          onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
          onOptionSelected = primaryEntryCallback,
          onConfirm = primaryEntryCallback,
          onCancel = viewModel::onCancel,
          multiProvider = uiState.enabledProviders.size > 1,
          onMoreOptionsSelected = viewModel::onMoreOptionsSelected
Loading