blob: caf43490e51f7228c21d16cdb141f48a3feadf3d [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.content.Intent
import android.net.Uri
import android.os.CancellationSignal
import androidx.annotation.RequiresApi
import androidx.credentials.exceptions.ClearCredentialException
import androidx.credentials.exceptions.ClearCredentialProviderConfigurationException
import androidx.credentials.exceptions.CreateCredentialException
import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException
import androidx.credentials.exceptions.GetCredentialException
import androidx.credentials.exceptions.GetCredentialProviderConfigurationException
import java.util.concurrent.Executor
/**
* 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
internal class CredentialManagerImpl internal constructor(
private val context: Context
) : CredentialManager {
companion object {
/**
* An intent action that shows a screen that let user enable a Credential Manager provider.
*/
private const val
INTENT_ACTION_FOR_CREDENTIAL_PROVIDER_SETTINGS: String =
"android.settings.CREDENTIAL_PROVIDER"
}
/**
* 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
*/
override fun getCredentialAsync(
context: Context,
request: GetCredentialRequest,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
) {
val provider: CredentialProvider? = CredentialProviderFactory(context)
.getBestAvailableProvider()
if (provider == null) {
callback.onError(
GetCredentialProviderConfigurationException(
"getCredentialAsync no provider dependencies found - please ensure " +
"the desired provider dependencies are added")
)
return
}
provider.onGetCredential(context, request, cancellationSignal, executor, callback)
}
/**
* 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)
override fun getCredentialAsync(
context: Context,
pendingGetCredentialHandle: PrepareGetCredentialResponse.PendingGetCredentialHandle,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
) {
val provider: CredentialProvider? = CredentialProviderFactory(context)
.getBestAvailableProvider(shouldFallbackToPreU = false)
if (provider == null) {
callback.onError(
GetCredentialProviderConfigurationException("No Credential Manager provider found")
)
return
}
provider.onGetCredential(
context, pendingGetCredentialHandle, cancellationSignal, executor, 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 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)
override fun prepareGetCredentialAsync(
request: GetCredentialRequest,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<PrepareGetCredentialResponse, GetCredentialException>,
) {
val provider: CredentialProvider? = CredentialProviderFactory(context)
.getBestAvailableProvider(shouldFallbackToPreU = false)
if (provider == null) {
callback.onError(
GetCredentialProviderConfigurationException("No Credential Manager provider found"))
return
}
provider.onPrepareCredential(request, cancellationSignal, executor, callback)
}
/**
* 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
*/
override fun createCredentialAsync(
context: Context,
request: CreateCredentialRequest,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>,
) {
val provider: CredentialProvider? = CredentialProviderFactory(this.context)
.getBestAvailableProvider()
if (provider == null) {
callback.onError(CreateCredentialProviderConfigurationException(
"createCredentialAsync no provider dependencies found - please ensure the " +
"desired provider dependencies are added"))
return
}
provider.onCreateCredential(context, request, cancellationSignal, executor, callback)
}
/**
* 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
*/
override fun clearCredentialStateAsync(
request: ClearCredentialStateRequest,
cancellationSignal: CancellationSignal?,
executor: Executor,
callback: CredentialManagerCallback<Void?, ClearCredentialException>,
) {
val provider: CredentialProvider? = CredentialProviderFactory(context)
.getBestAvailableProvider()
if (provider == null) {
callback.onError(ClearCredentialProviderConfigurationException(
"clearCredentialStateAsync no provider dependencies found - please ensure the " +
"desired provider dependencies are added"))
return
}
provider.onClearCredential(request, cancellationSignal, executor, callback)
}
/**
* 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)
override fun createSettingsPendingIntent(): PendingIntent {
val intent: Intent = Intent(INTENT_ACTION_FOR_CREDENTIAL_PROVIDER_SETTINGS)
intent.setData(Uri.parse("package:" + context.getPackageName()))
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
}
}