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

Commit 0bc03e30 authored by Helen Qin's avatar Helen Qin
Browse files

Use the launch intent data in the ux.

Note: The intent data is not a full list of everything we need yet so
the ux is looking a bit raw operating on the only information it can
get. Following up will be improving this.

Test: deployed locally
Bug: 247855226
Change-Id: Icd66866d37210486f3c5223c9e93b9b589476f96
parent 9db605bb
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -36,6 +36,12 @@ public class RequestInfo implements Parcelable {
     */
    public static final @NonNull String EXTRA_REQUEST_INFO =
            "android.credentials.ui.extra.REQUEST_INFO";
    /**
     * The intent extra key for the {@code ResultReceiver} object when launching the UX
     * activities.
     */
    public static final @NonNull String EXTRA_RESULT_RECEIVER =
            "android.credentials.ui.extra.RESULT_RECEIVER";

    /** Type value for an executeGetCredential request. */
    public static final @NonNull String TYPE_GET = "android.credentials.ui.TYPE_GET";
+55 −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

import android.app.slice.Slice
import android.credentials.ui.Entry
import android.graphics.drawable.Icon

/**
 * UI representation for a credential entry used during the get credential flow.
 *
 * TODO: move to jetpack.
 */
class CredentialEntryUi(
  val userName: CharSequence,
  val displayName: CharSequence?,
  val icon: Icon?,
  // TODO: add last used.
) {
  companion object {
    fun fromSlice(slice: Slice): CredentialEntryUi {
      val items = slice.items

      var title: String? = null
      var subTitle: String? = null
      var icon: Icon? = null

      items.forEach {
        if (it.hasHint(Entry.HINT_ICON)) {
          icon = it.icon
        } else if (it.hasHint(Entry.HINT_SUBTITLE)) {
          subTitle = it.text.toString()
        } else if (it.hasHint(Entry.HINT_TITLE)) {
          title = it.text.toString()
        }
      }
      // TODO: fail NPE more elegantly.
      return CredentialEntryUi(title!!, subTitle, icon)
    }
  }
}
+49 −142
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

import android.app.slice.Slice
@@ -9,11 +25,8 @@ import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
import android.graphics.drawable.Icon
import android.os.Binder
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.createflow.CreatePasskeyUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.ProviderInfo
import com.android.credentialmanager.getflow.CredentialOptionInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState

@@ -41,6 +54,39 @@ class CredentialManagerRepo(
    ) ?: testProviderList()
  }

  fun getCredentialInitialUiState(): GetCredentialUiState {
    val providerList = GetFlowUtils.toProviderList(providerList, context)
    return GetCredentialUiState(
      providerList,
      GetScreenState.CREDENTIAL_SELECTION,
      providerList.first()
    )
  }

  fun createPasskeyInitialUiState(): CreatePasskeyUiState {
    val providerList = CreateFlowUtils.toProviderList(providerList, context)
    return CreatePasskeyUiState(
      providers = providerList,
      currentScreenState = CreateScreenState.PASSKEY_INTRO,
    )
  }

  companion object {
    lateinit var repo: CredentialManagerRepo

    fun setup(
      context: Context,
      intent: Intent,
    ) {
      repo = CredentialManagerRepo(context, intent)
    }

    fun getInstance(): CredentialManagerRepo {
      return repo
    }
  }

  // TODO: below are prototype functionalities. To be removed for productionization.
  private fun testProviderList(): List<ProviderData> {
    return listOf(
      ProviderData(
@@ -94,143 +140,4 @@ class CredentialManagerRepo(
      slice
    )
  }

  private fun getCredentialProviderList():
    List<com.android.credentialmanager.getflow.ProviderInfo> {
      return listOf(
        com.android.credentialmanager.getflow.ProviderInfo(
          icon = context.getDrawable(R.drawable.ic_passkey)!!,
          name = "Google Password Manager",
          appDomainName = "tribank.us",
          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
          credentialOptions = listOf(
            CredentialOptionInfo(
              icon = context.getDrawable(R.drawable.ic_passkey)!!,
              title = "Elisa Backett",
              subtitle = "elisa.beckett@gmail.com",
              id = "id-1",
            ),
            CredentialOptionInfo(
              icon = context.getDrawable(R.drawable.ic_passkey)!!,
              title = "Elisa Backett Work",
              subtitle = "elisa.beckett.work@google.com",
              id = "id-2",
            ),
          )
        ),
        com.android.credentialmanager.getflow.ProviderInfo(
          icon = context.getDrawable(R.drawable.ic_passkey)!!,
          name = "Lastpass",
          appDomainName = "tribank.us",
          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
          credentialOptions = listOf(
            CredentialOptionInfo(
              icon = context.getDrawable(R.drawable.ic_passkey)!!,
              title = "Elisa Backett",
              subtitle = "elisa.beckett@lastpass.com",
              id = "id-1",
            ),
          )
        ),
        com.android.credentialmanager.getflow.ProviderInfo(
          icon = context.getDrawable(R.drawable.ic_passkey)!!,
          name = "Dashlane",
          appDomainName = "tribank.us",
          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
          credentialOptions = listOf(
            CredentialOptionInfo(
              icon = context.getDrawable(R.drawable.ic_passkey)!!,
              title = "Elisa Backett",
              subtitle = "elisa.beckett@dashlane.com",
              id = "id-1",
            ),
          )
        ),
      )
  }

  private fun createCredentialProviderList(): List<ProviderInfo> {
    return listOf(
      ProviderInfo(
        icon = context.getDrawable(R.drawable.ic_passkey)!!,
        name = "Google Password Manager",
        appDomainName = "tribank.us",
        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
        createOptions = listOf(
          CreateOptionInfo(
            icon = context.getDrawable(R.drawable.ic_passkey)!!,
            title = "Elisa Backett",
            subtitle = "elisa.beckett@gmail.com",
            id = "id-1",
          ),
          CreateOptionInfo(
            icon = context.getDrawable(R.drawable.ic_passkey)!!,
            title = "Elisa Backett Work",
            subtitle = "elisa.beckett.work@google.com",
            id = "id-2",
          ),
        )
      ),
      ProviderInfo(
        icon = context.getDrawable(R.drawable.ic_passkey)!!,
        name = "Lastpass",
        appDomainName = "tribank.us",
        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
        createOptions = listOf(
          CreateOptionInfo(
            icon = context.getDrawable(R.drawable.ic_passkey)!!,
            title = "Elisa Backett",
            subtitle = "elisa.beckett@lastpass.com",
            id = "id-1",
          ),
        )
      ),
      ProviderInfo(
        icon = context.getDrawable(R.drawable.ic_passkey)!!,
        name = "Dashlane",
        appDomainName = "tribank.us",
        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
        createOptions = listOf(
          CreateOptionInfo(
            icon = context.getDrawable(R.drawable.ic_passkey)!!,
            title = "Elisa Backett",
            subtitle = "elisa.beckett@dashlane.com",
            id = "id-1",
          ),
        )
      ),
    )
  }

  fun getCredentialInitialUiState(): GetCredentialUiState {
    val providerList = getCredentialProviderList()
    return GetCredentialUiState(
      providerList,
      GetScreenState.CREDENTIAL_SELECTION,
      providerList.first()
    )
  }

  fun createPasskeyInitialUiState(): CreatePasskeyUiState {
    val providerList = createCredentialProviderList()
    return CreatePasskeyUiState(
      providers = providerList,
      currentScreenState = CreateScreenState.PASSKEY_INTRO,
    )
  }

  companion object {
    lateinit var repo: CredentialManagerRepo

    fun setup(
      context: Context,
      intent: Intent,
    ) {
      repo = CredentialManagerRepo(context, intent)
    }

    fun getInstance(): CredentialManagerRepo {
      return repo
    }
  }
}
+31 −8
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

import android.credentials.ui.RequestInfo
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
@@ -16,14 +33,20 @@ class CredentialSelectorActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    CredentialManagerRepo.setup(this, intent)
    val startDestination = intent.extras?.getString(
      "start_destination",
      "CREATE_PASSKEY"
    ) ?: "CREATE_PASSKEY"

    val requestInfo = intent.extras?.getParcelable<RequestInfo>(RequestInfo.EXTRA_REQUEST_INFO)
    if (requestInfo != null) {
      val requestType = requestInfo.type
      setContent {
        CredentialSelectorTheme {
        CredentialManagerBottomSheet(startDestination)
          CredentialManagerBottomSheet(requestType)
        }
      }
    } else {
      // TODO: prototype only code to be removed. In production should exit.
      setContent {
        CredentialSelectorTheme {
          CredentialManagerBottomSheet(RequestInfo.TYPE_CREATE)
        }
      }
    }
  }
+106 −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

import android.content.Context
import android.credentials.ui.Entry
import android.credentials.ui.ProviderData
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.getflow.CredentialOptionInfo
import com.android.credentialmanager.getflow.ProviderInfo

/** Utility functions for converting CredentialManager data structures to or from UI formats. */
class GetFlowUtils {
  companion object {

    fun toProviderList(
      providerDataList: List<ProviderData>,
      context: Context,
    ): List<ProviderInfo> {
      return providerDataList.map {
        ProviderInfo(
          // TODO: replace to extract from the service data structure when available
          icon = context.getDrawable(R.drawable.ic_passkey)!!,
          name = it.packageName,
          appDomainName = "tribank.us",
          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
          credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context)
        )
      }
    }


    /* From service data structure to UI credential entry list representation. */
    private fun toCredentialOptionInfoList(
      credentialEntries: List<Entry>,
      context: Context,
    ): List<CredentialOptionInfo> {
      return credentialEntries.map {
        val credentialEntryUi = CredentialEntryUi.fromSlice(it.slice)

        // Consider directly move the UI object into the class.
        return@map CredentialOptionInfo(
          // TODO: remove fallbacks
          icon = credentialEntryUi.icon?.loadDrawable(context)
            ?: context.getDrawable(R.drawable.ic_passkey)!!,
          title = credentialEntryUi.userName.toString(),
          subtitle = credentialEntryUi.displayName?.toString() ?: "Unknown display name",
          id = it.entryId,
        )
      }
    }
  }
}

class CreateFlowUtils {
  companion object {

    fun toProviderList(
      providerDataList: List<ProviderData>,
      context: Context,
    ): List<com.android.credentialmanager.createflow.ProviderInfo> {
      return providerDataList.map {
        com.android.credentialmanager.createflow.ProviderInfo(
          // TODO: replace to extract from the service data structure when available
          icon = context.getDrawable(R.drawable.ic_passkey)!!,
          name = it.packageName,
          appDomainName = "tribank.us",
          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
          createOptions = toCreationOptionInfoList(it.credentialEntries, context),
        )
      }
    }

    private fun toCreationOptionInfoList(
      creationEntries: List<Entry>,
      context: Context,
    ): List<CreateOptionInfo> {
      return creationEntries.map {
        val saveEntryUi = SaveEntryUi.fromSlice(it.slice)

        return@map CreateOptionInfo(
          // TODO: remove fallbacks
          icon = saveEntryUi.icon?.loadDrawable(context)
            ?: context.getDrawable(R.drawable.ic_passkey)!!,
          title = saveEntryUi.title.toString(),
          subtitle = saveEntryUi.subTitle?.toString() ?: "Unknown subtitle",
          id = it.entryId,
        )
      }
    }
  }
}
Loading