1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.credentials.playservices.controllers 18 19 import android.app.Activity 20 import android.content.Context 21 import android.os.Bundle 22 import android.os.CancellationSignal 23 import androidx.credentials.CredentialManagerCallback 24 import androidx.credentials.exceptions.CreateCredentialCancellationException 25 import androidx.credentials.exceptions.CreateCredentialException 26 import androidx.credentials.exceptions.CreateCredentialUnknownException 27 import androidx.credentials.exceptions.GetCredentialCancellationException 28 import androidx.credentials.exceptions.GetCredentialException 29 import androidx.credentials.exceptions.GetCredentialUnknownException 30 import androidx.credentials.playservices.CredentialProviderPlayServicesImpl 31 import java.util.concurrent.Executor 32 33 /** 34 * Extensible abstract class for credential controllers. Please implement this class per every 35 * request/response credential type. Unique logic is left to the use case of the implementation. If 36 * you are building your own version as an OEM, the below can be mimicked to your own credential 37 * provider equivalent and whatever internal service you invoke. 38 * 39 * @param T1 the credential request type from credential manager 40 * @param T2 the credential request type converted to play services 41 * @param R2 the credential response type from play services 42 * @param R1 the credential response type converted back to that used by credential manager 43 * @param E1 the credential error type to throw 44 */ 45 @Suppress("deprecation") 46 internal abstract class CredentialProviderController< 47 T1 : Any, 48 T2 : Any, 49 R2 : Any, 50 R1 : Any, 51 E1 : Any 52 >(private val context: Context) : CredentialProviderBaseController(context) { 53 54 companion object { 55 56 internal const val ERROR_MESSAGE_START_ACTIVITY_FAILED = 57 "Failed to launch the selector UI. Hint: ensure the `context` parameter is an" + 58 " Activity-based context." 59 60 /** 61 * This handles result code exception reporting across all create flows. 62 * 63 * @return a boolean indicating if the create flow contains a result code exception 64 */ 65 @JvmStatic maybeReportErrorResultCodeCreatenull66 protected fun maybeReportErrorResultCodeCreate( 67 resultCode: Int, 68 cancelOnError: (CancellationSignal?, () -> Unit) -> Unit, 69 onError: (CreateCredentialException) -> Unit, 70 cancellationSignal: CancellationSignal? 71 ): Boolean { 72 if (resultCode != Activity.RESULT_OK) { 73 var exception: CreateCredentialException = 74 CreateCredentialUnknownException(generateErrorStringUnknown(resultCode)) 75 if (resultCode == Activity.RESULT_CANCELED) { 76 exception = CreateCredentialCancellationException(generateErrorStringCanceled()) 77 } 78 cancelOnError(cancellationSignal) { onError(exception) } 79 return true 80 } 81 return false 82 } 83 generateErrorStringUnknownnull84 internal fun generateErrorStringUnknown(resultCode: Int): String { 85 return "activity with result code: $resultCode indicating not RESULT_OK" 86 } 87 generateErrorStringCancelednull88 internal fun generateErrorStringCanceled(): String { 89 return "activity is cancelled by the user." 90 } 91 92 /** 93 * This allows catching result code errors from the get flow if they exist. 94 * 95 * @return a boolean indicating if the get flow had an error 96 */ 97 @JvmStatic maybeReportErrorResultCodeGetnull98 protected fun maybeReportErrorResultCodeGet( 99 resultCode: Int, 100 cancelOnError: (CancellationSignal?, () -> Unit) -> Unit, 101 onError: (GetCredentialException) -> Unit, 102 cancellationSignal: CancellationSignal? 103 ): Boolean { 104 if (resultCode != Activity.RESULT_OK) { 105 var exception: GetCredentialException = 106 GetCredentialUnknownException(generateErrorStringUnknown(resultCode)) 107 if (resultCode == Activity.RESULT_CANCELED) { 108 exception = GetCredentialCancellationException(generateErrorStringCanceled()) 109 } 110 cancelOnError(cancellationSignal) { onError(exception) } 111 return true 112 } 113 return false 114 } 115 116 /** 117 * This will check for cancellation, and will otherwise set a result to the callback, or an 118 * exception. 119 */ 120 @JvmStatic cancelOrCallbackExceptionOrResultnull121 protected fun cancelOrCallbackExceptionOrResult( 122 cancellationSignal: CancellationSignal?, 123 onResultOrException: () -> Unit 124 ) { 125 if (CredentialProviderPlayServicesImpl.cancellationReviewer(cancellationSignal)) { 126 return 127 } 128 onResultOrException() 129 } 130 } 131 132 /** 133 * To avoid redundant logic across all controllers for exceptions parceled back from the hidden 134 * activity, this can be generally implemented. 135 * 136 * @return a boolean indicating if an error was reported or not by the result receiver 137 */ maybeReportErrorFromResultReceivernull138 protected fun maybeReportErrorFromResultReceiver( 139 resultData: Bundle, 140 conversionFn: (String?, String?) -> E1, 141 executor: Executor, 142 callback: CredentialManagerCallback<R1, E1>, 143 cancellationSignal: CancellationSignal? 144 ): Boolean { 145 val isError = resultData.getBoolean(FAILURE_RESPONSE_TAG) 146 if (!isError) { 147 return false 148 } 149 val errType = resultData.getString(EXCEPTION_TYPE_TAG) 150 val errMsg = resultData.getString(EXCEPTION_MESSAGE_TAG) 151 val exception = conversionFn(errType, errMsg) 152 cancelOrCallbackExceptionOrResult( 153 cancellationSignal = cancellationSignal, 154 onResultOrException = { executor.execute { callback.onError(exception) } } 155 ) 156 return true 157 } 158 159 /** 160 * Invokes the flow that starts retrieving credential data. In this use case, we invoke play 161 * service modules. 162 * 163 * @param request a credential provider request 164 * @param callback a credential manager callback with a credential provider response 165 * @param executor to be used in any multi-threaded operation calls, such as listenable futures 166 */ invokePlayServicesnull167 abstract fun invokePlayServices( 168 request: T1, 169 callback: CredentialManagerCallback<R1, E1>, 170 executor: Executor, 171 cancellationSignal: CancellationSignal? 172 ) 173 174 /** 175 * Allows converting from a credential provider request to a play service request. 176 * 177 * @param request a credential provider request 178 * @return a play service request 179 */ 180 protected abstract fun convertRequestToPlayServices(request: T1): T2 181 182 /** 183 * Allows converting from a play service response to a credential provider response. 184 * 185 * @param response a play service response 186 * @return a credential provider response 187 */ 188 protected abstract fun convertResponseToCredentialManager(response: R2): R1 189 } 190