RemoteCredentialEntry.kt
/*
* Copyright 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 androidx.credentials.provider
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.slice.Slice
import android.app.slice.SliceSpec
import android.net.Uri
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import android.util.Log
import androidx.annotation.NonNull
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.credentials.PublicKeyCredential
import java.util.Collections
/**
* An entry on the selector, denoting that the credential will be retrieved from a remote device.
* A public key credential entry that is displayed on the account selector UI.
*
* Once this entry is selected, the corresponding [pendingIntent] will be invoked. The provider
* can then show any activity they wish to. Before finishing the activity, provider must
* set the final [androidx.credentials.GetCredentialResponse] through the
* [PendingIntentHandler.setGetCredentialResponse] helper API.
*
* @property pendingIntent the [PendingIntent] to be invoked when the user selects
* this entry
*
* See [android.service.credentials.BeginGetCredentialResponse] for usage details.
*/
@RequiresApi(34)
class RemoteCredentialEntry constructor(
val pendingIntent: PendingIntent,
beginGetPublicKeyCredentialOption: BeginGetPublicKeyCredentialOption
) : android.service.credentials.CredentialEntry(
beginGetPublicKeyCredentialOption,
toSlice(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, pendingIntent,
beginGetPublicKeyCredentialOption)
) {
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(@NonNull dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
}
@Suppress("AcronymName")
companion object CREATOR {
private const val TAG = "RemoteEntry"
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val SLICE_HINT_PENDING_INTENT =
"androidx.credentials.provider.remoteEntry.SLICE_HINT_PENDING_INTENT"
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val SLICE_HINT_OPTION_ID =
"androidx.credentials.provider.remoteEntry.SLICE_HINT_OPTION_ID"
/** @hide */
@JvmStatic
internal fun toSlice(
type: String,
pendingIntent: PendingIntent,
beginGetPublicKeyCredentialOption: BeginGetPublicKeyCredentialOption
): Slice {
// TODO("Put the right spec and version value")
val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec(type, 1))
sliceBuilder.addAction(pendingIntent,
Slice.Builder(sliceBuilder)
.addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT))
.build(), /*subType=*/null)
.addText(beginGetPublicKeyCredentialOption.id,
/*subType=*/null,
listOf(SLICE_HINT_OPTION_ID))
return sliceBuilder.build()
}
/**
* Returns an instance of [RemoteCredentialEntry] derived from a [Slice] object.
*
* @param slice the [Slice] object constructed through [toSlice]
*
* @hide
*/
@SuppressLint("WrongConstant") // custom conversion between jetpack and framework
@JvmStatic
fun fromSlice(slice: Slice): RemoteCredentialEntry? {
var beginGetPublicKeyCredentialOptionId: CharSequence? = null
var pendingIntent: PendingIntent? = null
slice.items.forEach {
if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
pendingIntent = it.action
} else if (it.hasHint(SLICE_HINT_OPTION_ID)) {
beginGetPublicKeyCredentialOptionId = it.text
}
}
return try {
RemoteCredentialEntry(pendingIntent!!,
BeginGetPublicKeyCredentialOption.createFromEntrySlice(
Bundle(),
beginGetPublicKeyCredentialOptionId!!.toString()
)
)
} catch (e: Exception) {
Log.i(TAG, "fromSlice failed with: " + e.message)
null
}
}
@JvmField val CREATOR: Parcelable.Creator<RemoteCredentialEntry> = object :
Parcelable.Creator<RemoteCredentialEntry> {
override fun createFromParcel(p0: Parcel?): RemoteCredentialEntry? {
val baseEntry =
android.service.credentials.CredentialEntry.CREATOR.createFromParcel(p0)
return fromSlice(baseEntry.slice)
}
@Suppress("ArrayReturn")
override fun newArray(size: Int): Array<RemoteCredentialEntry?> {
return arrayOfNulls(size)
}
}
}
}