CredentialEntry.kt

/*
 * Copyright 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 androidx.credentials.provider

import android.app.slice.Slice
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.credentials.PasswordCredential.Companion.TYPE_PASSWORD_CREDENTIAL
import androidx.credentials.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
import androidx.credentials.R

/**
 * Base class for a credential entry to be displayed on
 * the selector.
 *
 * The [entryGroupId] allows the credential selector display to, in the case of multiple entries
 * across providers that have the same [entryGroupId] value, trim down to a single, most recently
 * used provider on the primary card, meant for quick authentication. This will also be used for
 * entry grouping display logic. However, if the user desires, it is possible to expand back the
 * entries and select the provider of their choice. This should be something directly linked to the
 * credential (e.g. [PublicKeyCredentialEntry] and [PasswordCredentialEntry] utilize 'username'),
 * and should allow variance only as far as the case of letters (i.e. Foo@gmail.com and
 * foo@gmail.com). These guidelines should be followed in cases where [CustomCredentialEntry] are
 * created.
 *
 * (RestrictTo property) type the type of the credential associated with this entry, e.g. a
 * [BeginGetPasswordOption] will have type [TYPE_PASSWORD_CREDENTIAL]
 * @property beginGetCredentialOption the option from the original [BeginGetCredentialRequest],
 * for which this credential entry is being added
 * @property entryGroupId an ID used for deduplication or to group entries during display
 * @property affiliatedDomain the user visible affiliated domain, a CharSequence
 * representation of a web domain or an app package name that the given credential in this
 * entry is associated with when it is different from the requesting entity, default null
 * @property isDefaultIconPreferredAsSingleProvider when set to true, the UI prefers to render the
 * default credential type icon when you are the only available provider; see individual subclasses
 * for these default icons (e.g. for [PublicKeyCredentialEntry], it is based on
 * [R.drawable.ic_password])
 */
abstract class CredentialEntry internal constructor(
    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    open val type: String,
    val beginGetCredentialOption: BeginGetCredentialOption,
    val entryGroupId: CharSequence,
    val isDefaultIconPreferredAsSingleProvider: Boolean,
    val affiliatedDomain: CharSequence? = null,
) {

    @RequiresApi(34)
    private object Api34Impl {
        @JvmStatic
        fun fromCredentialEntry(credentialEntry: android.service.credentials.CredentialEntry):
            CredentialEntry? {
            val slice = credentialEntry.slice
            return fromSlice(slice)
        }
    }
    companion object {

        /**
         * Converts a framework [android.service.credentials.CredentialEntry] class to a Jetpack
         * [CredentialEntry] class
         *
         * Note that this API is not needed in a general credential retrieval flow that is
         * implemented using this jetpack library, where you are only required to construct
         * an instance of [CredentialEntry] to populate the [BeginGetCredentialResponse].
         *
         * @param credentialEntry the instance of framework class to be converted
         */
        @JvmStatic
        fun fromCredentialEntry(credentialEntry: android.service.credentials.CredentialEntry):
            CredentialEntry? {
            if (Build.VERSION.SDK_INT >= 34) {
                return Api34Impl.fromCredentialEntry(credentialEntry)
            }
            return null
        }

        @JvmStatic
        @RequiresApi(28)
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        internal fun fromSlice(slice: Slice): CredentialEntry? {
            return try {
                when (slice.spec?.type) {
                    TYPE_PASSWORD_CREDENTIAL -> PasswordCredentialEntry.fromSlice(slice)!!
                    TYPE_PUBLIC_KEY_CREDENTIAL -> PublicKeyCredentialEntry.fromSlice(slice)!!
                    else -> CustomCredentialEntry.fromSlice(slice)!!
                }
            } catch (e: Exception) {
                // Try CustomCredentialEntry.fromSlice one last time in case the cause was a failed
                // password / passkey parsing attempt.
                CustomCredentialEntry.fromSlice(slice)
            }
        }

        @JvmStatic
        @RequiresApi(28)
        internal fun toSlice(entry: CredentialEntry): Slice? {
            when (entry) {
                is PasswordCredentialEntry -> return PasswordCredentialEntry.toSlice(entry)
                is PublicKeyCredentialEntry -> return PublicKeyCredentialEntry.toSlice(entry)
                is CustomCredentialEntry -> return CustomCredentialEntry.toSlice(entry)
            }
            return null
        }
    }
}