blob: 1c910c3bf8a5f1e0d46d6b497382315d2c0aeca3 [file] [log] [blame]
/*
* 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
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.os.CancellationSignal
import androidx.annotation.RequiresApi
import androidx.credentials.exceptions.ClearCredentialException
import androidx.credentials.exceptions.CreateCredentialException
import androidx.credentials.exceptions.GetCredentialException
import java.util.concurrent.Executor
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
/**
* Manages user authentication flows.
*
* An application can call the CredentialManager apis to launch framework UI flows for a user to
* register a new credential or to consent to a saved credential from supported credential
* providers, which can then be used to authenticate to the app.
*
* This class contains its own exception types. They represent unique failures during the Credential
* Manager flow. As required, they can be extended for unique types containing new and unique
* versions of the exception - either with new 'exception types' (same credential class, different
* exceptions), or inner subclasses and their exception types (a subclass credential class and all
* their exception types).
*
* For example, if there is an UNKNOWN exception type, assuming the base Exception is
* [ClearCredentialException], we can add an 'exception type' class for it as follows:
* ```
* class ClearCredentialUnknownException(
* errorMessage: CharSequence? = null
* ) : ClearCredentialException(TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION, errorMessage) {
* // ...Any required impl here...//
* companion object {
* private const val TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION: String =
* "androidx.credentials.TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION"
* }
* }
* ```
*
* Furthermore, the base class can be subclassed to a new more specific credential type, which then
* can further be subclassed into individual exception types. The first is an example of a 'inner
* credential type exception', and the next is a 'exception type' of this subclass exception.
*
* ```
* class UniqueCredentialBasedOnClearCredentialException(
* type: String,
* errorMessage: CharSequence? = null
* ) : ClearCredentialException(type, errorMessage) {
* // ... Any required impl here...//
* }
* // .... code and logic .... //
* class UniqueCredentialBasedOnClearCredentialUnknownException(
* errorMessage: CharSequence? = null
* ) : ClearCredentialException(TYPE_UNIQUE_CREDENTIAL_BASED_ON_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION,
* errorMessage) {
* // ... Any required impl here ... //
* companion object {
* private const val
* TYPE_UNIQUE_CREDENTIAL_BASED_ON_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION: String =
* "androidx.credentials.TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION"
* }
* }
* ```
*/
@SuppressLint("ObsoleteSdkInt") // Accommodate dependencies with a lower min sdk requirement
interface CredentialManager {
companion object {
/**
* Creates a [CredentialManager] based on the given [context].
*
* @param context the context with which the CredentialManager should be associated
*/
@JvmStatic fun create(context: Context): CredentialManager = CredentialManagerImpl(context)
}
/**
* Requests a credential from the user.
*
* The execution potentially launches framework UI flows for a user to view available
* credentials, consent to using one of them, etc.
*
* @sample androidx.credentials.samples.callGetCredential
* @param context the context used to launch any UI needed; use an activity context to make sure
* the UI will be launched within the same task stack
* @param request the request for getting the credential
* @throws GetCredentialException If the request fails
*/
suspend fun getCredential(
context: Context,
request: GetCredentialRequest,
): GetCredentialResponse = suspendCancellableCoroutine { continuation ->
// Any Android API that supports cancellation should be configured to propagate
// coroutine cancellation as follows:
val canceller = CancellationSignal()
continuation.invokeOnCancellation { canceller.cancel() }
val callback =
object : CredentialManagerCallback<GetCredentialResponse, GetCredentialException> {
override fun onResult(result: GetCredentialResponse) {
if (continuation.isActive) {
continuation.resume(result)
}
}
override fun onError(e: GetCredentialException) {
if (continuation.isActive) {
continuation.resumeWithException(e)
}
}
}
getCredentialAsync(
context,
request,
canceller,
// Use a direct executor to avoid extra dispatch. Resuming the continuation will
// handle getting to the right thread or pool via the ContinuationInterceptor.
Runnable::run,
callback
)
}
/**
* Requests a credential from the user.
*
* Different from the other `getCredential(GetCredentialRequest, Activity)` API, this API
* launches the remaining flows to retrieve an app credential from the user, after the completed
* prefetch work corresponding to the given `pendingGetCredentialHandle`. Use this API to
* complete the full credential retrieval operation after you initiated a request through the
* [prepareGetCredential] API.
*
* The execution can potentially launch UI flows to collect user consent to using a credential,
* display a picker when multiple credentials exist, etc.
*
* @param context the context used to launch any UI needed; use an activity context to make sure
* the UI will be launched within the same task stack
* @param pendingGetCredentialHandle the handle representing the pending operation to resume
* @throws GetCredentialException If the request fails
*/
@RequiresApi(34)
suspend fun getCredential(
context: Context,
pendingGetCredentialHandle: PrepareGetCredentialResponse.PendingGetCredentialHandle,
): GetCredentialResponse = suspendCancellableCoroutine { continuation ->
// Any Android API that supports cancellation should be configured to propagate
// coroutine cancellation as follows:
val canceller = CancellationSignal()
continuation.invokeOnCancellation { canceller.cancel() }
val callback =
object : CredentialManagerCallback<GetCredentialResponse, GetCredentialException> {
override fun onResult(result: GetCredentialResponse) {
if (continuation.isActive) {
continuation.resume(result)
}
}
override fun onError(e: GetCredentialException) {
if (continuation.isActive) {
continuation.resumeWithException(e)
}
}
}
getCredentialAsync(
context,
pendingGetCredentialHandle,
canceller,
// Use a direct executor to avoid extra dispatch. Resuming the continuation will
// handle getting to the right thread or pool via the ContinuationInterceptor.
Runnable::run,
callback
)
}
/**
* Prepares for a get-credential operation. Returns a [PrepareGetCredentialResponse] that can
* later be used to launch the credential retrieval UI flow to finalize a user credential for
* your app.
*
* This API doesn't invoke any UI. It only performs the preparation work so that you can later
* launch the remaining get-credential operation (involves UIs) through the [getCredential] API
* which incurs less latency than executing the whole operation in one call.
*
* @param request the request for getting the credential
* @throws GetCredentialException If the request fails
*/
@RequiresApi(34)
suspend fun prepareGetCredential(
request: GetCredentialRequest,
): PrepareGetCredentialResponse = suspendCancellableCoroutine { continuation ->
// Any Android API that supports cancellation should be configured to propagate
// coroutine cancellation as follows:
val canceller = CancellationSignal()
continuation.invokeOnCancellation { canceller.cancel() }
val callback =
object :
CredentialManagerCallback<PrepareGetCredentialResponse, GetCredentialException> {
override fun onResult(result: PrepareGetCredentialResponse) {
if (continuation.isActive) {
continuation.resume(result)
}
}
override fun onError(e: GetCredentialException) {
if (continuation.isActive) {
continuation.resumeWithException(e)
}
}
}
prepareGetCredentialAsync(
request,
canceller,
// Use a direct executor to avoid extra dispatch. Resuming the continuation will
// handle getting to the right thread or pool via the ContinuationInterceptor.
Runnable::run,
callback
)
}
/**
* Registers a user credential that can be used to authenticate the user to the app in the
* future.
*
* The execution potentially launches framework UI flows for a user to view their registration
* options, grant consent, etc.
*
* @param context the context used to launch any UI needed; use an activity context to make sure
* the UI will be launched within the same task stack
* @param request the request for creating the credential
* @throws CreateCredentialException If the request fails
*/
suspend fun createCredential(
context: Context,
request: CreateCredentialRequest,
): CreateCredentialResponse = suspendCancellableCoroutine { continuation ->
// Any Android API that supports cancellation should be configured to propagate
// coroutine cancellation as follows:
val canceller = CancellationSignal()
continuation.invokeOnCancellation { canceller.cancel() }
val callback =
object :
CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> {
override fun onResult(result: CreateCredentialResponse) {
if (continuation.isActive) {
continuation.resume(result)
}
}
override fun onError(e: CreateCredentialException) {
if (continuation.isActive) {
continuation.resumeWithException(e)
}
}
}
createCredentialAsync(
context,
request,
canceller,
// Use a direct executor to avoid extra dispatch. Resuming the continuation will
// handle getting to the right thread or pool via the ContinuationInterceptor.
Runnable::run,
callback
)
}
/**
* Clears the current user credential state from all credential providers.
*
* You should invoked this api after your user signs out of your app to notify all credential
* providers that any stored credential session for the given app should be cleared.
*
* A credential provider may have stored an active credential session and use it to limit
* sign-in options for future get-credential calls. For example, it may prioritize the active
* credential over any other available credential. When your user explicitly signs out of your
* app and in order to get the holistic sign-in options the next time, you should call this API
* to let the provider clear any stored credential session.
*
* If the API is called with [ClearCredentialRequestTypes.CLEAR_RESTORE_CREDENTIAL] then any
* restore credential stored on device will be cleared.
*
* @param request the request for clearing the app user's credential state
* @throws ClearCredentialException If the request fails
*/
suspend fun clearCredentialState(request: ClearCredentialStateRequest): Unit =
suspendCancellableCoroutine { continuation ->
// Any Android API that supports cancellation should be configured to propagate
// coroutine cancellation as follows:
val canceller = CancellationSignal()
continuation.invokeOnCancellation { canceller.cancel() }
val callback =
object : CredentialManagerCallback<Void?, ClearCredentialException> {
override fun onResult(result: Void?) {
if (continuation.isActive) {
continuation.resume(Unit)
}
}
override fun onError(e: ClearCredentialException) {
if (continuation.isActive) {
continuation.resumeWithException(e)
}
}
}
clearCredentialStateAsync(
request,
canceller,
// Use a direct executor to avoid extra dispatch. Resuming the continuation will
// handle getting to the right thread or pool via the ContinuationInterceptor.
Runnable::run,
callback
)
}
/**
* Requests a credential from the user.
*
* This API uses callbacks instead of Kotlin coroutines.
*
* The execution potentially launches framework UI flows for a user to view available
* credentials, consent to using one of them, etc.
*
* @param context the context used to launch any UI needed; use an activity context to make sure
* the UI will be launched within the same task stack
* @param request the request for getting the credential
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this executor
* @param callback the callback invoked when the request succeeds or fails
*/
fun getCredentialAsync(
context: Context,
request: GetCredentialRequest,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
)
/**
* Requests a credential from the user.
*
* This API uses callbacks instead of Kotlin coroutines.
*
* Different from the other `getCredentialAsync(GetCredentialRequest, Activity)` API, this API
* launches the remaining flows to retrieve an app credential from the user, after the completed
* prefetch work corresponding to the given `pendingGetCredentialHandle`. Use this API to
* complete the full credential retrieval operation after you initiated a request through the
* [prepareGetCredentialAsync] API.
*
* The execution can potentially launch UI flows to collect user consent to using a credential,
* display a picker when multiple credentials exist, etc.
*
* @param context the context used to launch any UI needed; use an activity context to make sure
* the UI will be launched within the same task stack
* @param pendingGetCredentialHandle the handle representing the pending operation to resume
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this executor
* @param callback the callback invoked when the request succeeds or fails
*/
@RequiresApi(34)
fun getCredentialAsync(
context: Context,
pendingGetCredentialHandle: PrepareGetCredentialResponse.PendingGetCredentialHandle,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
)
/**
* Prepares for a get-credential operation. Returns a [PrepareGetCredentialResponse] that can
* later be used to launch the credential retrieval UI flow to finalize a user credential for
* your app.
*
* This API uses callbacks instead of Kotlin coroutines.
*
* This API doesn't invoke any UI. It only performs the preparation work so that you can later
* launch the remaining get-credential operation (involves UIs) through the [getCredentialAsync]
* API which incurs less latency than executing the whole operation in one call.
*
* @param request the request for getting the credential
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this executor
* @param callback the callback invoked when the request succeeds or fails
*/
@RequiresApi(34)
fun prepareGetCredentialAsync(
request: GetCredentialRequest,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<PrepareGetCredentialResponse, GetCredentialException>,
)
/**
* Registers a user credential that can be used to authenticate the user to the app in the
* future.
*
* This API uses callbacks instead of Kotlin coroutines.
*
* The execution potentially launches framework UI flows for a user to view their registration
* options, grant consent, etc.
*
* @param context the context used to launch any UI needed; use an activity context to make sure
* the UI will be launched within the same task stack
* @param request the request for creating the credential
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this executor
* @param callback the callback invoked when the request succeeds or fails
*/
fun createCredentialAsync(
context: Context,
request: CreateCredentialRequest,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>,
)
/**
* Clears the current user credential state from all credential providers.
*
* This API uses callbacks instead of Kotlin coroutines.
*
* You should invoked this api after your user signs out of your app to notify all credential
* providers that any stored credential session for the given app should be cleared.
*
* A credential provider may have stored an active credential session and use it to limit
* sign-in options for future get-credential calls. For example, it may prioritize the active
* credential over any other available credential. When your user explicitly signs out of your
* app and in order to get the holistic sign-in options the next time, you should call this API
* to let the provider clear any stored credential session.
*
* @param request the request for clearing the app user's credential state
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this executor
* @param callback the callback invoked when the request succeeds or fails
*/
fun clearCredentialStateAsync(
request: ClearCredentialStateRequest,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<Void?, ClearCredentialException>,
)
/**
* Returns a pending intent that shows a screen that lets a user enable a Credential Manager
* provider.
*
* @return the pending intent that can be launched
*/
@RequiresApi(34) fun createSettingsPendingIntent(): PendingIntent
}