BeginGetCredentialResponse.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.os.Build
import android.os.Bundle
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.credentials.provider.utils.BeginGetCredentialUtil
/**
* Response from a credential provider to [BeginGetCredentialRequest], containing credential
* entries and other associated entries/data to be shown on the account selector UI.
*
* @constructor constructs an instance of [BeginGetCredentialResponse]
*
* @param credentialEntries the list of credential entries to be shown on the selector UI, whereby
* each entry is set to provide a potential credential corresponding to a given
* [BeginGetCredentialOption] from the original [BeginGetCredentialRequest]
* @param actions the list of action entries to be shown on the selector UI, whereby each entry
* is set to provide an action that the user can perform before retrieving the credential, e.g.
* selecting a credential from a provider UI
* @param authenticationActions the list of authentication actions to be shown on the selector UI,
* whereby each entry is set to denote an account/group that is currently locked and cannot
* return any credentials, allowing the user to select one of these entries and unlock another
* set of credentials
* @param remoteEntry the entry that is set to allow retrieving a credential from another device
*/
class BeginGetCredentialResponse constructor(
val credentialEntries: List<CredentialEntry> = listOf(),
val actions: List<Action> = listOf(),
val authenticationActions: List<AuthenticationAction> = listOf(),
val remoteEntry: RemoteEntry? = null
) {
/** Builder for [BeginGetCredentialResponse]. */
class Builder {
private var credentialEntries: MutableList<CredentialEntry> = mutableListOf()
private var actions: MutableList<Action> = mutableListOf()
private var authenticationActions: MutableList<AuthenticationAction> = mutableListOf()
private var remoteEntry: RemoteEntry? = null
/**
* Sets a remote credential entry to be shown on the UI. Provider must set this if they
* wish to get the credential from a different device.
*
* When constructing the [CredentialEntry] object, the pending intent
* must be set such that it leads to an activity that can provide UI to fulfill the request
* on a remote device. When user selects this [remoteEntry], the system will
* invoke the pending intent set on the [CredentialEntry].
*
* <p> Once the remote credential flow is complete, the [android.app.Activity]
* result should be set to [android.app.Activity#RESULT_OK] and an extra with the
* [CredentialProviderService#EXTRA_GET_CREDENTIAL_RESPONSE] key should be populated
* with a [android.credentials.Credential] object.
*
* <p> Note that as a provider service you will only be able to set a remote entry if :
* - Provider service possesses the
* [android.Manifest.permission.PROVIDE_REMOTE_CREDENTIALS] permission.
* - Provider service is configured as the provider that can provide remote entries.
*
* If the above conditions are not met, setting back [BeginGetCredentialResponse]
* on the callback from [CredentialProviderService#onBeginGetCredential] will
* throw a [SecurityException].
*/
fun setRemoteEntry(remoteEntry: RemoteEntry?): Builder {
this.remoteEntry = remoteEntry
return this
}
/**
* Adds a [CredentialEntry] to the list of entries to be displayed on the UI.
*/
fun addCredentialEntry(entry: CredentialEntry): Builder {
credentialEntries.add(entry)
return this
}
/**
* Sets the list of credential entries to be displayed on the account selector UI.
*/
fun setCredentialEntries(entries: List<CredentialEntry>): Builder {
credentialEntries = entries.toMutableList()
return this
}
/**
* Adds an [Action] to the list of actions to be displayed on
* the UI.
*
* <p> An [Action] must be used for independent user actions,
* such as opening the app, intenting directly into a certain app activity etc. The
* pending intent set with the [action] must invoke the corresponding activity.
*/
fun addAction(action: Action): Builder {
this.actions.add(action)
return this
}
/**
* Sets the list of actions to be displayed on the UI.
*/
fun setActions(actions: List<Action>): Builder {
this.actions = actions.toMutableList()
return this
}
/**
* Add an authentication entry to be shown on the UI. Providers must set this entry if
* the corresponding account is locked and no underlying credentials can be returned.
*
* <p> When the user selects this [authenticationAction], the system invokes the
* corresponding pending intent.
* Once the authentication action activity is launched, and the user is authenticated,
* providers should create another response with [BeginGetCredentialResponse] using
* this time adding the unlocked credentials in the form of [CredentialEntry]'s.
*
* <p>The new response object must be set on the authentication activity's
* result. The result code should be set to [android.app.Activity#RESULT_OK] and
* the [CredentialProviderService#EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE] extra
* should be set with the new fully populated [BeginGetCredentialResponse] object.
*/
fun addAuthenticationAction(authenticationAction: AuthenticationAction): Builder {
this.authenticationActions.add(authenticationAction)
return this
}
/**
* Sets the list of authentication entries to be displayed on the account selector UI.
*/
fun setAuthenticationActions(authenticationEntries: List<AuthenticationAction>): Builder {
this.authenticationActions = authenticationEntries.toMutableList()
return this
}
/**
* Builds a [BeginGetCredentialResponse] instance.
*/
fun build(): BeginGetCredentialResponse {
return BeginGetCredentialResponse(
credentialEntries.toList(),
actions.toList(),
authenticationActions.toList(),
remoteEntry
)
}
}
@RequiresApi(34)
private object Api34Impl {
private const val REQUEST_KEY = "androidx.credentials.provider.BeginGetCredentialResponse"
@JvmStatic
@DoNotInline
fun asBundle(bundle: Bundle, response: BeginGetCredentialResponse) {
bundle.putParcelable(
REQUEST_KEY,
BeginGetCredentialUtil.convertToFrameworkResponse(response)
)
}
@JvmStatic
@DoNotInline
fun fromBundle(bundle: Bundle): BeginGetCredentialResponse? {
val frameworkResponse = bundle.getParcelable(
REQUEST_KEY,
android.service.credentials.BeginGetCredentialResponse::class.java
)
if (frameworkResponse != null) {
return BeginGetCredentialUtil.convertToJetpackResponse(frameworkResponse)
}
return null
}
}
companion object {
/**
* Helper method to convert the class to a parcelable [Bundle], in case the class
* instance needs to be sent across a process. Consumers of this method should use
* [fromBundle] to reconstruct the class instance back from the bundle returned here.
*/
@JvmStatic
fun asBundle(response: BeginGetCredentialResponse): Bundle {
val bundle = Bundle()
if (Build.VERSION.SDK_INT >= 34) { // Android U
Api34Impl.asBundle(bundle, response)
}
return bundle
}
/**
* Helper method to convert a [Bundle] retrieved through [asBundle], back
* to an instance of [BeginGetCredentialResponse].
*/
@JvmStatic
fun fromBundle(bundle: Bundle): BeginGetCredentialResponse? {
if (Build.VERSION.SDK_INT >= 34) { // Android U
return Api34Impl.fromBundle(bundle)
}
return null
}
}
}