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

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

Merge "Fix the ui test data flow."

parents 19601015 65f3c064
Loading
Loading
Loading
Loading
+99 −124
Original line number Diff line number Diff line
@@ -47,6 +47,10 @@ import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
import com.android.credentialmanager.jetpack.provider.Action
import com.android.credentialmanager.jetpack.provider.CreateEntry
import com.android.credentialmanager.jetpack.provider.CredentialCountInformation
import com.android.credentialmanager.jetpack.provider.CredentialEntry

// Consider repo per screen, similar to view model?
class CredentialManagerRepo(
@@ -63,7 +67,7 @@ class CredentialManagerRepo(
    requestInfo = intent.extras?.getParcelable(
      RequestInfo.EXTRA_REQUEST_INFO,
      RequestInfo::class.java
    ) ?: testCreatePasskeyRequestInfo()
    ) ?: testGetRequestInfo()

    providerEnabledList = when (requestInfo.type) {
      RequestInfo.TYPE_CREATE ->
@@ -265,18 +269,12 @@ class CredentialManagerRepo(
            text: String,
            subtext: String? = null,
    ): Entry {
    val slice = Slice.Builder(
      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
    ).addText(
      text, null, listOf(Entry.HINT_ACTION_TITLE)
    )
    if (subtext != null) {
      slice.addText(subtext, null, listOf(Entry.HINT_ACTION_SUBTEXT))
    }
        val action = Action(text, subtext, null)

        return Entry(
                key,
                subkey,
      slice.build()
                Action.toSlice(action)
        )
    }

@@ -312,26 +310,15 @@ class CredentialManagerRepo(
                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(
      credentialTypeDisplayName, null, listOf(Entry.HINT_CREDENTIAL_TYPE_DISPLAY_NAME)
    ).addText(
      userName, null, listOf(Entry.HINT_USER_NAME)
    ).addIcon(
      Icon.createWithResource(context, R.drawable.ic_passkey),
      null,
      listOf(Entry.HINT_PROFILE_ICON))
    if (userDisplayName != null) {
      slice.addText(userDisplayName, null, listOf(Entry.HINT_PASSKEY_USER_DISPLAY_NAME))
    }
    if (lastUsedTimeMillis != null) {
      slice.addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
    }
        val credentialEntry = CredentialEntry(credentialType, credentialTypeDisplayName, userName,
                userDisplayName, pendingIntent, lastUsedTimeMillis
                ?: 0L, Icon.createWithResource(context, R.drawable.ic_passkey),
                false)

        return Entry(
                key,
                subkey,
      slice.build(),
                CredentialEntry.toSlice(credentialEntry),
                pendingIntent,
                null
        )
@@ -358,33 +345,21 @@ class CredentialManagerRepo(
                TYPE_PASSWORD_CREDENTIAL,
                toBundle("beckett-bakert@gmail.com", "password123")
        )
    val fillInIntent = Intent().putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
        val fillInIntent = Intent().putExtra(
                CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
                createPasswordRequest)

    val slice = Slice.Builder(
      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
    ).addText(
        providerDisplayName, null, listOf(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME))
      .addIcon(
        Icon.createWithResource(context, R.drawable.ic_passkey),
        null,
        listOf(Entry.HINT_CREDENTIAL_TYPE_ICON))
      .addIcon(
        Icon.createWithResource(context, R.drawable.ic_profile),
        null,
        listOf(Entry.HINT_PROFILE_ICON))
      .addInt(
        passwordCount, null, listOf(Entry.HINT_PASSWORD_COUNT))
      .addInt(
        passkeyCount, null, listOf(Entry.HINT_PASSKEY_COUNT))
      .addInt(
        totalCredentialCount, null, listOf(Entry.HINT_TOTAL_CREDENTIAL_COUNT))
      .addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
      .build()
        val createEntry = CreateEntry(
                providerDisplayName, pendingIntent,
                Icon.createWithResource(context, R.drawable.ic_profile), lastUsedTimeMillis,
                listOf(
                        CredentialCountInformation.createPasswordCountInformation(passwordCount),
                        CredentialCountInformation.createPublicKeyCountInformation(passkeyCount),
                ))
        return Entry(
                key,
                subkey,
      slice,
                CreateEntry.toSlice(createEntry),
                pendingIntent,
                fillInIntent,
        )
+29 −22
Original line number Diff line number Diff line
@@ -42,9 +42,10 @@ import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
import com.android.credentialmanager.jetpack.provider.ActionUi
import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
import com.android.credentialmanager.jetpack.provider.SaveEntryUi
import com.android.credentialmanager.jetpack.provider.Action
import com.android.credentialmanager.jetpack.provider.CredentialCountInformation
import com.android.credentialmanager.jetpack.provider.CredentialEntry
import com.android.credentialmanager.jetpack.provider.CreateEntry
import org.json.JSONObject

/** Utility functions for converting CredentialManager data structures to or from UI formats. */
@@ -107,7 +108,8 @@ class GetFlowUtils {
      context: Context,
    ): List<CredentialEntryInfo> {
      return credentialEntries.map {
        val credentialEntryUi = CredentialEntryUi.fromSlice(it.slice)
        // TODO: handle NPE gracefully
        val credentialEntry = CredentialEntry.fromSlice(it.slice)!!

        // Consider directly move the UI object into the class.
        return@map CredentialEntryInfo(
@@ -116,14 +118,14 @@ class GetFlowUtils {
          entrySubkey = it.subkey,
          pendingIntent = it.pendingIntent,
          fillInIntent = it.frameworkExtrasIntent,
          credentialType = credentialEntryUi.credentialType.toString(),
          credentialTypeDisplayName = credentialEntryUi.credentialTypeDisplayName.toString(),
          userName = credentialEntryUi.userName.toString(),
          displayName = credentialEntryUi.userDisplayName?.toString(),
          credentialType = credentialEntry.type.toString(),
          credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
          userName = credentialEntry.username.toString(),
          displayName = credentialEntry.displayName?.toString(),
          // TODO: proper fallback
          icon = credentialEntryUi.entryIcon?.loadDrawable(context)
          icon = credentialEntry.icon?.loadDrawable(context)
                  ?: context.getDrawable(R.drawable.ic_other_sign_in)!!,
          lastUsedTimeMillis = credentialEntryUi.lastUsedTimeMillis,
          lastUsedTimeMillis = credentialEntry.lastUsedTimeMillis,
        )
      }
    }
@@ -170,7 +172,8 @@ class GetFlowUtils {
      providerIcon: Drawable,
    ): List<ActionEntryInfo> {
      return actionEntries.map {
        val actionEntryUi = ActionUi.fromSlice(it.slice)
        // TODO: handle NPE gracefully
        val actionEntryUi = Action.fromSlice(it.slice)!!

        return@map ActionEntryInfo(
          providerId = providerId,
@@ -178,10 +181,10 @@ class GetFlowUtils {
          entrySubkey = it.subkey,
          pendingIntent = it.pendingIntent,
          fillInIntent = it.frameworkExtrasIntent,
          title = actionEntryUi.text.toString(),
          title = actionEntryUi.title.toString(),
          // TODO: gracefully fail
          icon = providerIcon,
          subTitle = actionEntryUi.subtext?.toString(),
          subTitle = actionEntryUi.subTitle?.toString(),
        )
      }
    }
@@ -383,7 +386,8 @@ class CreateFlowUtils {
      context: Context,
    ): List<CreateOptionInfo> {
      return creationEntries.map {
        val saveEntryUi = SaveEntryUi.fromSlice(it.slice)
        // TODO: handle NPE gracefully
        val createEntry = CreateEntry.fromSlice(it.slice)!!

        return@map CreateOptionInfo(
          // TODO: remove fallbacks
@@ -392,13 +396,16 @@ class CreateFlowUtils {
          entrySubkey = it.subkey,
          pendingIntent = it.pendingIntent,
          fillInIntent = it.frameworkExtrasIntent,
          userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
          profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
          userProviderDisplayName = createEntry.accountName.toString(),
          profileIcon = createEntry.icon?.loadDrawable(context)
                  ?: requestDisplayInfo.typeIcon,
          passwordCount = saveEntryUi.passwordCount ?: 0,
          passkeyCount = saveEntryUi.passkeyCount ?: 0,
          totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
          lastUsedTimeMillis = saveEntryUi.lastUsedTimeMillis ?: 0,
          passwordCount = CredentialCountInformation.getPasswordCount(
                  createEntry.credentialCountInformationList) ?: 0,
          passkeyCount = CredentialCountInformation.getPasskeyCount(
                  createEntry.credentialCountInformationList) ?: 0,
          totalCredentialCount = CredentialCountInformation.getTotalCount(
                  createEntry.credentialCountInformationList) ?: 0,
          lastUsedTimeMillis = createEntry.lastUsedTimeMillis ?: 0,
        )
      }
    }
+55 −0
Original line number Diff line number Diff line
@@ -14,35 +14,42 @@
 * limitations under the License.
 */

package com.android.credentialmanager.jetpack.provider
package com.android.credentialmanager.jetpack.developer

import android.app.slice.Slice
import android.credentials.ui.Entry
import android.os.Bundle

/**
 * UI representation for a credential entry used during the get credential flow.
 *
 * TODO: move to jetpack.
 */
class ActionUi(
  val text: CharSequence,
  val subtext: CharSequence?,
) {
class PasswordCredential constructor(
        val id: String,
        val password: String,
) : Credential(android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL, toBundle(id, password)) {

    init {
        require(password.isNotEmpty()) { "password should not be empty" }
    }

    /** @hide */
    companion object {
    fun fromSlice(slice: Slice): ActionUi {
      var text: CharSequence? = null
      var subtext: CharSequence? = null

      val items = slice.items
      items.forEach {
        if (it.hasHint(Entry.HINT_ACTION_TITLE)) {
          text = it.text
        } else if (it.hasHint(Entry.HINT_ACTION_SUBTEXT)) {
          subtext = it.text

        const val BUNDLE_KEY_ID = "androidx.credentials.BUNDLE_KEY_ID"
        const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD"

        @JvmStatic
        internal fun toBundle(id: String, password: String): Bundle {
            val bundle = Bundle()
            bundle.putString(BUNDLE_KEY_ID, id)
            bundle.putString(BUNDLE_KEY_PASSWORD, password)
            return bundle
        }

        @JvmStatic
        internal fun createFrom(data: Bundle): PasswordCredential {
            try {
                val id = data.getString(BUNDLE_KEY_ID)
                val password = data.getString(BUNDLE_KEY_PASSWORD)
                return PasswordCredential(id!!, password!!)
            } catch (e: Exception) {
                throw FrameworkClassParsingException()
            }
      // TODO: fail NPE more elegantly.
      return ActionUi(text!!, subtext)
        }
    }
}
 No newline at end of file
+100 −0
Original line number Diff line number Diff line
@@ -16,65 +16,85 @@

package com.android.credentialmanager.jetpack.provider

import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.slice.Slice
import android.graphics.drawable.Icon
import android.app.slice.SliceSpec
import android.net.Uri
import android.util.Log
import java.util.Collections

/**
 * UI representation for a save entry used during the create credential flow.
 * UI representation for a credential entry used during the get credential flow.
 *
 * TODO: move to jetpack.
 */
class SaveEntryUi(
  val userProviderAccountName: CharSequence?,
  val credentialTypeIcon: Icon?,
  val profileIcon: Icon?,
  val passwordCount: Int?,
  val passkeyCount: Int?,
  val totalCredentialCount: Int?,
  val lastUsedTimeMillis: Long?,
class Action constructor(
        val title: CharSequence,
        val subTitle: CharSequence?,
        val pendingIntent: PendingIntent?,
) {

  init {
    require(title.isNotEmpty()) { "title must not be empty" }
  }

  companion object {
    const val SLICE_HINT_ACCOUNT_NAME =
            "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME"
    const val SLICE_HINT_ICON =
            "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON"
    const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION =
            "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION"
    const val SLICE_HINT_LAST_USED_TIME_MILLIS =
            "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
    const val SLICE_HINT_PENDING_INTENT =
            "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT"
    private const val TAG = "Action"
    internal const val SLICE_HINT_TITLE =
            "androidx.credentials.provider.action.HINT_ACTION_TITLE"
    internal const val SLICE_HINT_SUBTITLE =
            "androidx.credentials.provider.action.HINT_ACTION_SUBTEXT"
    internal const val SLICE_HINT_PENDING_INTENT =
            "androidx.credentials.provider.action.SLICE_HINT_PENDING_INTENT"

    @JvmStatic
    fun toSlice(action: Action): Slice {
      // TODO("Put the right spec and version value")
      val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("type", 1))
              .addText(action.title, /*subType=*/null,
                      listOf(SLICE_HINT_TITLE))
              .addText(action.subTitle, /*subType=*/null,
                      listOf(SLICE_HINT_SUBTITLE))
      if (action.pendingIntent != null) {
        sliceBuilder.addAction(action.pendingIntent,
                Slice.Builder(sliceBuilder)
                        .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT))
                        .build(),
                /*subType=*/null)
      }
      return sliceBuilder.build()
    }

    /**
     * Returns an instance of [SaveEntryUi] derived from a [Slice] object.
     * Returns an instance of [Action] derived from a [Slice] object.
     *
     * @param slice the [Slice] object constructed through the jetpack library
     * @param slice the [Slice] object constructed through [toSlice]
     */
    @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
    @JvmStatic
    fun fromSlice(slice: Slice): SaveEntryUi {
      var accountName: CharSequence? = null
      var icon: Icon? = null
    fun fromSlice(slice: Slice): Action? {
      // TODO("Put the right spec and version value")
      var title: CharSequence = ""
      var subTitle: CharSequence? = null
      var pendingIntent: PendingIntent? = null
      var lastUsedTimeMillis: Long = 0

      slice.items.forEach {
        if (it.hasHint(SLICE_HINT_ACCOUNT_NAME)) {
          accountName = it.text
        } else if (it.hasHint(SLICE_HINT_ICON)) {
          icon = it.icon
        if (it.hasHint(SLICE_HINT_TITLE)) {
          title = it.text
        } else if (it.hasHint(SLICE_HINT_SUBTITLE)) {
          subTitle = it.text
        } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
          pendingIntent = it.action
        } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) {
          lastUsedTimeMillis = it.long
        }
      }

      return SaveEntryUi(
              // TODO: Add count parsing
              accountName!!, icon, icon,
              0, 0, 0, lastUsedTimeMillis,
      )
      return try {
        Action(title, subTitle, pendingIntent)
      } catch (e: Exception) {
        Log.i(TAG, "fromSlice failed with: " + e.message)
        null
      }
    }
  }
}
+226 −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.jetpack.provider

import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.slice.Slice
import android.app.slice.SliceSpec
import android.graphics.drawable.Icon
import android.net.Uri
import android.os.Bundle
import android.util.Log
import java.util.Collections

/**
 * UI representation for a save entry used during the create credential flow.
 *
 * TODO: move to jetpack.
 */
class CreateEntry internal constructor(
        val accountName: CharSequence,
        val pendingIntent: PendingIntent?,
        val icon: Icon?,
        val lastUsedTimeMillis: Long,
        val credentialCountInformationList: List<CredentialCountInformation>
) {

  init {
    require(accountName.isNotEmpty()) { "accountName must not be empty" }
  }

  /**
   * A builder for [CreateEntry]
   *
   * @property accountName the name of the account where the credential will be registered
   * @property pendingIntent the [PendingIntent] that will be fired when the user selects
   * this entry
   *
   * @hide
   */
  class Builder constructor(
          private val accountName: CharSequence,
          private val pendingIntent: PendingIntent? = null
  ) {

    private var credentialCountInformationList: MutableList<CredentialCountInformation> =
            mutableListOf()
    private var icon: Icon? = null
    private var lastUsedTimeMillis: Long = 0

    /** Adds a [CredentialCountInformation] denoting a given credential
     * type and the count of credentials that the provider has stored for that
     * credential type.
     *
     * This information will be displayed on the [CreateEntry] to help the user
     * make a choice.
     */
    @Suppress("MissingGetterMatchingBuilder")
    fun addCredentialCountInformation(info: CredentialCountInformation): Builder {
      credentialCountInformationList.add(info)
      return this
    }

    /** Sets a list of [CredentialCountInformation]. Each item in the list denotes a given
     * credential type and the count of credentials that the provider has stored of that
     * credential type.
     *
     * This information will be displayed on the [CreateEntry] to help the user
     * make a choice.
     */
    fun setCredentialCountInformationList(infoList: List<CredentialCountInformation>): Builder {
      credentialCountInformationList = infoList as MutableList<CredentialCountInformation>
      return this
    }

    /** Sets an icon to be displayed with the entry on the UI */
    fun setIcon(icon: Icon?): Builder {
      this.icon = icon
      return this
    }

    /** Sets the last time this account was used */
    fun setLastUsedTimeMillis(lastUsedTimeMillis: Long): Builder {
      this.lastUsedTimeMillis = lastUsedTimeMillis
      return this
    }

    /**
     * Builds an instance of [CreateEntry]
     *
     * @throws IllegalArgumentException If [accountName] is empty
     */
    fun build(): CreateEntry {
      return CreateEntry(accountName, pendingIntent, icon, lastUsedTimeMillis,
              credentialCountInformationList)
    }
  }

  companion object {
    private const val TAG = "CreateEntry"
    internal const val SLICE_HINT_ACCOUNT_NAME =
            "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME"
    internal const val SLICE_HINT_ICON =
            "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON"
    internal const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION =
            "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION"
    internal const val SLICE_HINT_LAST_USED_TIME_MILLIS =
            "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
    internal const val SLICE_HINT_PENDING_INTENT =
            "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT"

    @JvmStatic
    fun toSlice(createEntry: CreateEntry): Slice {
      // TODO("Use the right type and revision")
      val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("type", 1))
      sliceBuilder.addText(createEntry.accountName, /*subType=*/null,
              listOf(SLICE_HINT_ACCOUNT_NAME))
              .addLong(createEntry.lastUsedTimeMillis, /*subType=*/null, listOf(
                      SLICE_HINT_LAST_USED_TIME_MILLIS))
      if (createEntry.icon != null) {
        sliceBuilder.addIcon(createEntry.icon, /*subType=*/null,
                listOf(SLICE_HINT_ICON))
      }

      val credentialCountBundle = convertCredentialCountInfoToBundle(
              createEntry.credentialCountInformationList)
      if (credentialCountBundle != null) {
        sliceBuilder.addBundle(convertCredentialCountInfoToBundle(
                createEntry.credentialCountInformationList), null, listOf(
                SLICE_HINT_CREDENTIAL_COUNT_INFORMATION))
      }
      if (createEntry.pendingIntent != null) {
        sliceBuilder.addAction(createEntry.pendingIntent,
                Slice.Builder(sliceBuilder)
                        .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT))
                        .build(),
                /*subType=*/null)
      }
      return sliceBuilder.build()
    }

    /**
     * Returns an instance of [CreateEntry] derived from a [Slice] object.
     *
     * @param slice the [Slice] object constructed through [toSlice]
     */
    @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
    @JvmStatic
    fun fromSlice(slice: Slice): CreateEntry? {
      // TODO("Put the right spec and version value")
      var accountName: CharSequence = ""
      var icon: Icon? = null
      var pendingIntent: PendingIntent? = null
      var credentialCountInfo: List<CredentialCountInformation> = listOf()
      var lastUsedTimeMillis: Long = 0

      slice.items.forEach {
        if (it.hasHint(SLICE_HINT_ACCOUNT_NAME)) {
          accountName = it.text
        } else if (it.hasHint(SLICE_HINT_ICON)) {
          icon = it.icon
        } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
          pendingIntent = it.action
        } else if (it.hasHint(SLICE_HINT_CREDENTIAL_COUNT_INFORMATION)) {
          credentialCountInfo = convertBundleToCredentialCountInfo(it.bundle)
        } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) {
          lastUsedTimeMillis = it.long
        }
      }

      return try {
        CreateEntry(accountName, pendingIntent, icon,
                lastUsedTimeMillis, credentialCountInfo)
      } catch (e: Exception) {
        Log.i(TAG, "fromSlice failed with: " + e.message)
        null
      }
    }

    @JvmStatic
    internal fun convertBundleToCredentialCountInfo(bundle: Bundle?):
            List<CredentialCountInformation> {
      val credentialCountList = ArrayList<CredentialCountInformation>()
      if (bundle == null) {
        return credentialCountList
      }
      bundle.keySet().forEach {
        try {
          credentialCountList.add(
                  CredentialCountInformation(it, bundle.getInt(it)))
        } catch (e: Exception) {
          Log.i(TAG, "Issue unpacking credential count info bundle: " + e.message)
        }
      }
      return credentialCountList
    }

    @JvmStatic
    internal fun convertCredentialCountInfoToBundle(
            credentialCountInformationList: List<CredentialCountInformation>
    ): Bundle? {
      if (credentialCountInformationList.isEmpty()) {
        return null
      }
      val bundle = Bundle()
      credentialCountInformationList.forEach {
        bundle.putInt(it.type, it.count)
      }
      return bundle
    }
  }
}
Loading