| /* |
| * 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.playservices.controllers |
| |
| import android.app.Activity |
| import android.os.Bundle |
| import android.os.CancellationSignal |
| import androidx.credentials.CredentialManagerCallback |
| import androidx.credentials.exceptions.CreateCredentialCancellationException |
| import androidx.credentials.exceptions.CreateCredentialException |
| import androidx.credentials.exceptions.CreateCredentialUnknownException |
| import androidx.credentials.exceptions.GetCredentialCancellationException |
| import androidx.credentials.exceptions.GetCredentialException |
| import androidx.credentials.exceptions.GetCredentialUnknownException |
| import androidx.credentials.playservices.CredentialProviderPlayServicesImpl |
| import java.util.concurrent.Executor |
| |
| /** |
| * Extensible abstract class for credential controllers. Please implement this class per every |
| * request/response credential type. Unique logic is left to the use case of the implementation. |
| * If you are building your own version as an OEM, the below can be mimicked to your own |
| * credential provider equivalent and whatever internal service you invoke. |
| * |
| * @param T1 the credential request type from credential manager |
| * @param T2 the credential request type converted to play services |
| * @param R2 the credential response type from play services |
| * @param R1 the credential response type converted back to that used by credential manager |
| * @param E1 the credential error type to throw |
| * |
| * @hide |
| */ |
| @Suppress("deprecation") |
| abstract class CredentialProviderController<T1 : Any, T2 : Any, R2 : Any, R1 : Any, |
| E1 : Any>(private val activity: Activity) : CredentialProviderBaseController(activity) { |
| |
| companion object { |
| |
| /** |
| * This handles result code exception reporting across all create flows. |
| * |
| * @return a boolean indicating if the create flow contains a result code exception |
| */ |
| @JvmStatic |
| protected fun maybeReportErrorResultCodeCreate( |
| resultCode: Int, |
| type: String, |
| cancelOnError: ( |
| CancellationSignal?, |
| () -> Unit |
| ) -> Unit, |
| onError: (CreateCredentialException) -> Unit, |
| cancellationSignal: CancellationSignal? |
| ): Boolean { |
| if (resultCode != Activity.RESULT_OK) { |
| var exception: CreateCredentialException = CreateCredentialUnknownException( |
| generateErrorStringUnknown(type, resultCode) |
| ) |
| if (resultCode == Activity.RESULT_CANCELED) { |
| exception = CreateCredentialCancellationException( |
| generateErrorStringCanceled(type) |
| ) |
| } |
| cancelOnError(cancellationSignal) { onError(exception) } |
| return true |
| } |
| return false |
| } |
| |
| internal fun generateErrorStringUnknown(type: String, resultCode: Int): String { |
| return "$type activity with result code: $resultCode indicating not RESULT_OK" |
| } |
| |
| internal fun generateErrorStringCanceled(type: String): String { |
| return "$type activity is cancelled by the user." |
| } |
| |
| /** |
| * This allows catching result code errors from the get flow if they exist. |
| * |
| * @return a boolean indicating if the get flow had an error |
| */ |
| @JvmStatic |
| protected fun maybeReportErrorResultCodeGet( |
| resultCode: Int, |
| type: String, |
| cancelOnError: ( |
| CancellationSignal?, |
| () -> Unit |
| ) -> Unit, |
| onError: (GetCredentialException) -> Unit, |
| cancellationSignal: CancellationSignal? |
| ): Boolean { |
| if (resultCode != Activity.RESULT_OK) { |
| var exception: GetCredentialException = GetCredentialUnknownException( |
| generateErrorStringUnknown(type, resultCode) |
| ) |
| if (resultCode == Activity.RESULT_CANCELED) { |
| exception = GetCredentialCancellationException( |
| generateErrorStringCanceled(type) |
| ) |
| } |
| cancelOnError(cancellationSignal) { onError(exception) } |
| return true |
| } |
| return false |
| } |
| |
| /** |
| * This will check for cancellation, and will otherwise set a result to the callback, or an |
| * exception. |
| */ |
| @JvmStatic |
| protected fun cancelOrCallbackExceptionOrResult( |
| cancellationSignal: CancellationSignal?, |
| onResultOrException: () -> Unit |
| ) { |
| if (CredentialProviderPlayServicesImpl.cancellationReviewer(cancellationSignal)) { |
| return |
| } |
| onResultOrException() |
| } |
| } |
| |
| /** |
| * To avoid redundant logic across all controllers for exceptions parceled back from the |
| * hidden activity, this can be generally implemented. |
| * |
| * @return a boolean indicating if an error was reported or not by the result receiver |
| */ |
| protected fun maybeReportErrorFromResultReceiver( |
| resultData: Bundle, |
| conversionFn: (String?, String?) -> E1, |
| executor: Executor, |
| callback: CredentialManagerCallback<R1, E1>, |
| cancellationSignal: CancellationSignal? |
| ): Boolean { |
| val isError = resultData.getBoolean(FAILURE_RESPONSE_TAG) |
| if (!isError) { |
| return false |
| } |
| val errType = resultData.getString(EXCEPTION_TYPE_TAG) |
| val errMsg = resultData.getString(EXCEPTION_MESSAGE_TAG) |
| val exception = conversionFn(errType, errMsg) |
| cancelOrCallbackExceptionOrResult(cancellationSignal) { |
| executor.execute { callback.onError(exception) } |
| } |
| return true |
| } |
| |
| /** |
| * Invokes the flow that starts retrieving credential data. In this use case, we invoke |
| * play service modules. |
| * |
| * @param request a credential provider request |
| * @param callback a credential manager callback with a credential provider response |
| * @param executor to be used in any multi-threaded operation calls, such as listenable futures |
| */ |
| abstract fun invokePlayServices( |
| request: T1, |
| callback: CredentialManagerCallback<R1, E1>, |
| executor: Executor, |
| cancellationSignal: CancellationSignal? |
| ) |
| |
| /** |
| * Allows converting from a credential provider request to a play service request. |
| * |
| * @param request a credential provider request |
| * @return a play service request |
| */ |
| protected abstract fun convertRequestToPlayServices(request: T1): T2 |
| |
| /** |
| * Allows converting from a play service response to a credential provider response. |
| * |
| * @param response a play service response |
| * @return a credential provider response |
| */ |
| protected abstract fun convertResponseToCredentialManager(response: R2): R1 |
| } |