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

Commit 20c29c93 authored by Qinmei Du's avatar Qinmei Du
Browse files

Add the only one createOption but has remoteEntry screen for create passkey

screenshot: https://screenshot.googleplex.com/TULWynJWxx4cAeP

Test: deployed locally

Bug: 253157211
Change-Id: I1865b89f71129ec94cac3ef0ea09a6b631eac65b
parent b5c35c65
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
  <string name="string_more_options">More options</string>
  <string name="string_create_in_another_place">Create in another place</string>
  <string name="string_save_to_another_place">Save to another place</string>
  <string name="string_use_another_device">Use another device</string>
  <string name="string_no_thanks">No thanks</string>
  <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
  <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
+8 −4
Original line number Diff line number Diff line
@@ -149,8 +149,8 @@ class CredentialManagerRepo(
      if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
    // TODO: covert from real requestInfo for create passkey
    var requestDisplayInfo = RequestDisplayInfo(
      "Elisa Beckett",
      "beckett-bakert@gmail.com",
      "Elisa Beckett",
      TYPE_PUBLIC_KEY_CREDENTIAL,
      "tribank")
    val createCredentialRequest = requestInfo.createCredentialRequest
@@ -165,8 +165,12 @@ class CredentialManagerRepo(
    return CreateCredentialUiState(
      enabledProviders = providerEnabledList,
      disabledProviders = providerDisabledList,
      if (hasDefault)
      {CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
      // TODO: Add the screen when defaultProvider has no createOption but
      //  there's remoteInfo under other providers
      if (!hasDefault || defaultProvider.createOptions.isEmpty()) {
        if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL)
        {CreateScreenState.PASSKEY_INTRO} else {CreateScreenState.PROVIDER_SELECTION}
      } else {CreateScreenState.CREATION_OPTION_SELECTION},
      requestDisplayInfo,
      if (hasDefault) {
        ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
@@ -439,7 +443,7 @@ class CredentialManagerRepo(
    return RequestInfo.newCreateRequestInfo(
      Binder(),
      CreateCredentialRequest(
        TYPE_PASSWORD_CREDENTIAL,
        TYPE_PUBLIC_KEY_CREDENTIAL,
        data
      ),
      /*isFirstUsage=*/false,
+7 −2
Original line number Diff line number Diff line
@@ -193,9 +193,10 @@ class CreateFlowUtils {
          icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
          name = it.providerFlattenedComponentName,
          displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
          createOptions = toCreationOptionInfoList(it.saveEntries, context),
          createOptions = toCreationOptionInfoList(
            it.providerFlattenedComponentName, it.saveEntries, context),
          isDefault = it.isDefaultProvider,
          remoteEntry = toRemoteInfo(it.remoteEntry),
          remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
        )
      }
    }
@@ -219,6 +220,7 @@ class CreateFlowUtils {
    }

    private fun toCreationOptionInfoList(
      providerId: String,
      creationEntries: List<Entry>,
      context: Context,
    ): List<CreateOptionInfo> {
@@ -227,6 +229,7 @@ class CreateFlowUtils {

        return@map CreateOptionInfo(
          // TODO: remove fallbacks
          providerId = providerId,
          entryKey = it.key,
          entrySubkey = it.subkey,
          pendingIntent = it.pendingIntent,
@@ -245,11 +248,13 @@ class CreateFlowUtils {
    }

    private fun toRemoteInfo(
      providerId: String,
      remoteEntry: Entry?,
    ): RemoteInfo? {
      // TODO: should also call fromSlice after getting the official jetpack code.
      return if (remoteEntry != null) {
        RemoteInfo(
          providerId = providerId,
          entryKey = remoteEntry.key,
          entrySubkey = remoteEntry.subkey,
          pendingIntent = remoteEntry.pendingIntent,
+63 −44
Original line number Diff line number Diff line
@@ -55,8 +55,11 @@ fun CreateCredentialScreen(
  viewModel: CreateCredentialViewModel,
  providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
  val primaryEntryCallback: () -> Unit = {
    viewModel.onPrimaryCreateOptionInfoSelected(providerActivityLauncher)
  val selectEntryCallback: (EntryInfo) -> Unit = {
    viewModel.onEntrySelected(it, providerActivityLauncher)
  }
  val confirmEntryCallback: () -> Unit = {
    viewModel.onConfirmCreationSelected(providerActivityLauncher)
  }
  val state = rememberModalBottomSheetState(
    initialValue = ModalBottomSheetValue.Expanded,
@@ -78,13 +81,13 @@ fun CreateCredentialScreen(
        )
        CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
          requestDisplayInfo = uiState.requestDisplayInfo,
          enabledProviderList = uiState.enabledProviders,
          providerInfo = uiState.activeEntry?.activeProvider!!,
          createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
          onOptionSelected = primaryEntryCallback,
          onConfirm = primaryEntryCallback,
          onOptionSelected = selectEntryCallback,
          onConfirm = confirmEntryCallback,
          onCancel = viewModel::onCancel,
          multiProvider = uiState.enabledProviders.size > 1,
          onMoreOptionsSelected = viewModel::onMoreOptionsSelected
          onMoreOptionsSelected = viewModel::onMoreOptionsSelected,
        )
        CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
          requestDisplayInfo = uiState.requestDisplayInfo,
@@ -93,7 +96,7 @@ fun CreateCredentialScreen(
          onBackButtonSelected = viewModel::onBackButtonSelected,
          onOptionSelected = viewModel::onMoreOptionsRowSelected,
          onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected,
            onRemoteEntrySelected = viewModel::onRemoteEntrySelected
          onRemoteEntrySelected = selectEntryCallback,
        )
        CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
          providerInfo = uiState.activeEntry?.activeProvider!!,
@@ -234,7 +237,7 @@ fun MoreOptionsSelectionCard(
  onBackButtonSelected: () -> Unit,
  onOptionSelected: (ActiveEntry) -> Unit,
  onDisabledPasswordManagerSelected: () -> Unit,
  onRemoteEntrySelected: () -> Unit,
  onRemoteEntrySelected: (EntryInfo) -> Unit,
) {
  Card() {
    Column() {
@@ -292,21 +295,19 @@ fun MoreOptionsSelectionCard(
              )
            }
          }
          var hasRemoteInfo = false
          // TODO: handle the error situation that if multiple remoteInfos exists
          enabledProviderList.forEach {
            if (it.remoteEntry != null) {
              hasRemoteInfo = true
            }
          }
          if (hasRemoteInfo) {
              item {
                RemoteEntryRow(
                  remoteInfo = it.remoteEntry!!,
                  onRemoteEntrySelected = onRemoteEntrySelected,
                )
              }
            }
          }
        }
      }
      Divider(
        thickness = 18.dp,
        color = Color.Transparent,
@@ -388,12 +389,12 @@ fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit
@Composable
fun CreationSelectionCard(
  requestDisplayInfo: RequestDisplayInfo,
  providerInfo: ProviderInfo,
  enabledProviderList: List<EnabledProviderInfo>,
  providerInfo: EnabledProviderInfo,
  createOptionInfo: CreateOptionInfo,
  onOptionSelected: () -> Unit,
  onOptionSelected: (EntryInfo) -> Unit,
  onConfirm: () -> Unit,
  onCancel: () -> Unit,
  multiProvider: Boolean,
  onMoreOptionsSelected: () -> Unit,
) {
  Card() {
@@ -442,19 +443,16 @@ fun CreationSelectionCard(
          .padding(horizontal = 24.dp)
          .align(alignment = Alignment.CenterHorizontally),
      ) {
        LazyColumn(
          verticalArrangement = Arrangement.spacedBy(2.dp)
        ) {
            item {
        PrimaryCreateOptionRow(
          requestDisplayInfo = requestDisplayInfo,
          createOptionInfo = createOptionInfo,
          onOptionSelected = onOptionSelected
        )
      }
        }
      }
      if (multiProvider) {
      var createOptionsSize = 0
      enabledProviderList.forEach{
        enabledProvider -> createOptionsSize += enabledProvider.createOptions.size}
      if (createOptionsSize > 1) {
        TextButton(
          onClick = onMoreOptionsSelected,
          modifier = Modifier
@@ -469,6 +467,26 @@ fun CreationSelectionCard(
            textAlign = TextAlign.Center,
          )
        }
      } else if (
        requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL
      ) {
        // TODO: handle the error situation that if multiple remoteInfos exists
        enabledProviderList.forEach { enabledProvider ->
          if (enabledProvider.remoteEntry != null) {
            TextButton(
              onClick = {
                onOptionSelected(enabledProvider.remoteEntry!!) },
              modifier = Modifier
                .padding(horizontal = 24.dp)
                .align(alignment = Alignment.CenterHorizontally)
            ) {
              Text(
                text = stringResource(R.string.string_use_another_device),
                textAlign = TextAlign.Center,
              )
            }
          }
        }
      }
      Divider(
        thickness = 24.dp,
@@ -501,10 +519,10 @@ fun CreationSelectionCard(
fun PrimaryCreateOptionRow(
  requestDisplayInfo: RequestDisplayInfo,
  createOptionInfo: CreateOptionInfo,
  onOptionSelected: () -> Unit
  onOptionSelected: (EntryInfo) -> Unit
) {
  Entry(
    onClick = onOptionSelected,
    onClick = {onOptionSelected(createOptionInfo)},
    icon = {
      Image(modifier = Modifier.size(32.dp).padding(start = 10.dp),
        bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
@@ -527,7 +545,7 @@ fun PrimaryCreateOptionRow(
          )
        } else {
          Text(
            text = requestDisplayInfo.subtitle,
            text = requestDisplayInfo.title,
            style = MaterialTheme.typography.titleLarge,
            modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
          )
@@ -640,10 +658,11 @@ fun MoreOptionsDisabledProvidersRow(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RemoteEntryRow(
  onRemoteEntrySelected: () -> Unit,
  remoteInfo: RemoteInfo,
  onRemoteEntrySelected: (RemoteInfo) -> Unit,
) {
  Entry(
    onClick = onRemoteEntrySelected,
    onClick = {onRemoteEntrySelected(remoteInfo)},
    icon = {
      Icon(
        painter = painterResource(R.drawable.ic_other_devices),
+34 −29
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ data class CreateCredentialUiState(
  val currentScreenState: CreateScreenState,
  val requestDisplayInfo: RequestDisplayInfo,
  val activeEntry: ActiveEntry? = null,
  val selectedEntry: EntryInfo? = null,
)

class CreateCredentialViewModel(
@@ -109,10 +110,6 @@ class CreateCredentialViewModel(
    // TODO: Complete this function
  }

  fun onRemoteEntrySelected() {
    // TODO: Complete this function
  }

  fun onCancel() {
    CredentialManagerRepo.getInstance().onCancel()
    dialogResult.value = DialogResult(ResultState.CANCELED)
@@ -125,25 +122,24 @@ class CreateCredentialViewModel(
    // TODO: implement the if choose as default or not logic later
  }

  fun onPrimaryCreateOptionInfoSelected(
  fun onEntrySelected(
    selectedEntry: EntryInfo,
    launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
  ) {
    val selectedEntry = uiState.activeEntry?.activeEntryInfo
    if (selectedEntry != null) {
    val providerId = selectedEntry.providerId
    val entryKey = selectedEntry.entryKey
    val entrySubkey = selectedEntry.entrySubkey
    Log.d(
        "Account Selector",
        "Option selected for creation: " +
                "{key = $entryKey, subkey = $entrySubkey}"
      )
      "Account Selector", "Option selected for entry: " +
              " {provider=$providerId, key=$entryKey, subkey=$entrySubkey")
    if (selectedEntry.pendingIntent != null) {
      uiState = uiState.copy(selectedEntry = selectedEntry)
      val intentSenderRequest = IntentSenderRequest.Builder(selectedEntry.pendingIntent)
        .setFillInIntent(selectedEntry.fillInIntent).build()
      launcher.launch(intentSenderRequest)
    } else {
      CredentialManagerRepo.getInstance().onOptionSelected(
          uiState.activeEntry?.activeProvider!!.name,
        providerId,
        entryKey,
        entrySubkey
      )
@@ -151,20 +147,29 @@ class CreateCredentialViewModel(
        ResultState.COMPLETE,
      )
    }
  }

  fun onConfirmCreationSelected(
    launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
  ) {
    val selectedEntry = uiState.activeEntry?.activeEntryInfo
    if (selectedEntry != null) {
      onEntrySelected(selectedEntry, launcher)
    } else {
      Log.w("Account Selector",
        "Illegal state: confirm is pressed but activeEntry isn't set.")
      dialogResult.value = DialogResult(
        ResultState.COMPLETE,
      )
      TODO("Gracefully handle illegal state.")
    }
  }

  fun onProviderActivityResult(providerActivityResult: ProviderActivityResult) {
    val entry = uiState.activeEntry?.activeEntryInfo
    val entry = uiState.selectedEntry
    val resultCode = providerActivityResult.resultCode
    val resultData = providerActivityResult.data
    val providerId = uiState.activeEntry?.activeProvider!!.name
    if (entry != null) {
      val providerId = entry.providerId
      Log.d("Account Selector", "Got provider activity result: {provider=" +
              "$providerId, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
              "resultCode=$resultCode, resultData=$resultData}"
Loading