1 /* <lambda>null2 * Copyright 2022 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 18 19 import android.annotation.SuppressLint 20 import android.app.PendingIntent 21 import android.content.Context 22 import android.os.CancellationSignal 23 import androidx.annotation.RequiresApi 24 import androidx.credentials.exceptions.ClearCredentialException 25 import androidx.credentials.exceptions.CreateCredentialException 26 import androidx.credentials.exceptions.GetCredentialException 27 import java.util.concurrent.Executor 28 import kotlin.coroutines.resume 29 import kotlin.coroutines.resumeWithException 30 import kotlinx.coroutines.suspendCancellableCoroutine 31 32 /** 33 * Manages user authentication flows. 34 * 35 * An application can call the CredentialManager apis to launch framework UI flows for a user to 36 * register a new credential or to consent to a saved credential from supported credential 37 * providers, which can then be used to authenticate to the app. 38 * 39 * This class contains its own exception types. They represent unique failures during the Credential 40 * Manager flow. As required, they can be extended for unique types containing new and unique 41 * versions of the exception - either with new 'exception types' (same credential class, different 42 * exceptions), or inner subclasses and their exception types (a subclass credential class and all 43 * their exception types). 44 * 45 * For example, if there is an UNKNOWN exception type, assuming the base Exception is 46 * [ClearCredentialException], we can add an 'exception type' class for it as follows: 47 * ``` 48 * class ClearCredentialUnknownException( 49 * errorMessage: CharSequence? = null 50 * ) : ClearCredentialException(TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION, errorMessage) { 51 * // ...Any required impl here...// 52 * companion object { 53 * private const val TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION: String = 54 * "androidx.credentials.TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION" 55 * } 56 * } 57 * ``` 58 * 59 * Furthermore, the base class can be subclassed to a new more specific credential type, which then 60 * can further be subclassed into individual exception types. The first is an example of a 'inner 61 * credential type exception', and the next is a 'exception type' of this subclass exception. 62 * 63 * ``` 64 * class UniqueCredentialBasedOnClearCredentialException( 65 * type: String, 66 * errorMessage: CharSequence? = null 67 * ) : ClearCredentialException(type, errorMessage) { 68 * // ... Any required impl here...// 69 * } 70 * // .... code and logic .... // 71 * class UniqueCredentialBasedOnClearCredentialUnknownException( 72 * errorMessage: CharSequence? = null 73 * ) : ClearCredentialException(TYPE_UNIQUE_CREDENTIAL_BASED_ON_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION, 74 * errorMessage) { 75 * // ... Any required impl here ... // 76 * companion object { 77 * private const val 78 * TYPE_UNIQUE_CREDENTIAL_BASED_ON_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION: String = 79 * "androidx.credentials.TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION" 80 * } 81 * } 82 * ``` 83 */ 84 @SuppressLint("ObsoleteSdkInt") // Accommodate dependencies with a lower min sdk requirement 85 interface CredentialManager { 86 companion object { 87 /** 88 * Creates a [CredentialManager] based on the given [context]. 89 * 90 * @param context the context with which the CredentialManager should be associated 91 */ 92 @JvmStatic fun create(context: Context): CredentialManager = CredentialManagerImpl(context) 93 } 94 95 /** 96 * Requests a credential from the user. 97 * 98 * The execution potentially launches framework UI flows for a user to view available 99 * credentials, consent to using one of them, etc. 100 * 101 * @sample androidx.credentials.samples.callGetCredential 102 * @param context the context used to launch any UI needed; use an activity context to make sure 103 * the UI will be launched within the same task stack 104 * @param request the request for getting the credential 105 * @throws GetCredentialException If the request fails 106 */ 107 suspend fun getCredential( 108 context: Context, 109 request: GetCredentialRequest, 110 ): GetCredentialResponse = suspendCancellableCoroutine { continuation -> 111 // Any Android API that supports cancellation should be configured to propagate 112 // coroutine cancellation as follows: 113 val canceller = CancellationSignal() 114 continuation.invokeOnCancellation { canceller.cancel() } 115 116 val callback = 117 object : CredentialManagerCallback<GetCredentialResponse, GetCredentialException> { 118 override fun onResult(result: GetCredentialResponse) { 119 if (continuation.isActive) { 120 continuation.resume(result) 121 } 122 } 123 124 override fun onError(e: GetCredentialException) { 125 if (continuation.isActive) { 126 continuation.resumeWithException(e) 127 } 128 } 129 } 130 131 getCredentialAsync( 132 context, 133 request, 134 canceller, 135 // Use a direct executor to avoid extra dispatch. Resuming the continuation will 136 // handle getting to the right thread or pool via the ContinuationInterceptor. 137 Runnable::run, 138 callback 139 ) 140 } 141 142 /** 143 * Requests a credential from the user. 144 * 145 * Different from the other `getCredential(GetCredentialRequest, Activity)` API, this API 146 * launches the remaining flows to retrieve an app credential from the user, after the completed 147 * prefetch work corresponding to the given `pendingGetCredentialHandle`. Use this API to 148 * complete the full credential retrieval operation after you initiated a request through the 149 * [prepareGetCredential] API. 150 * 151 * The execution can potentially launch UI flows to collect user consent to using a credential, 152 * display a picker when multiple credentials exist, etc. 153 * 154 * @param context the context used to launch any UI needed; use an activity context to make sure 155 * the UI will be launched within the same task stack 156 * @param pendingGetCredentialHandle the handle representing the pending operation to resume 157 * @throws GetCredentialException If the request fails 158 */ 159 @RequiresApi(34) 160 suspend fun getCredential( 161 context: Context, 162 pendingGetCredentialHandle: PrepareGetCredentialResponse.PendingGetCredentialHandle, 163 ): GetCredentialResponse = suspendCancellableCoroutine { continuation -> 164 // Any Android API that supports cancellation should be configured to propagate 165 // coroutine cancellation as follows: 166 val canceller = CancellationSignal() 167 continuation.invokeOnCancellation { canceller.cancel() } 168 169 val callback = 170 object : CredentialManagerCallback<GetCredentialResponse, GetCredentialException> { 171 override fun onResult(result: GetCredentialResponse) { 172 if (continuation.isActive) { 173 continuation.resume(result) 174 } 175 } 176 177 override fun onError(e: GetCredentialException) { 178 if (continuation.isActive) { 179 continuation.resumeWithException(e) 180 } 181 } 182 } 183 184 getCredentialAsync( 185 context, 186 pendingGetCredentialHandle, 187 canceller, 188 // Use a direct executor to avoid extra dispatch. Resuming the continuation will 189 // handle getting to the right thread or pool via the ContinuationInterceptor. 190 Runnable::run, 191 callback 192 ) 193 } 194 195 /** 196 * Prepares for a get-credential operation. Returns a [PrepareGetCredentialResponse] that can 197 * later be used to launch the credential retrieval UI flow to finalize a user credential for 198 * your app. 199 * 200 * This API doesn't invoke any UI. It only performs the preparation work so that you can later 201 * launch the remaining get-credential operation (involves UIs) through the [getCredential] API 202 * which incurs less latency than executing the whole operation in one call. 203 * 204 * @param request the request for getting the credential 205 * @throws GetCredentialException If the request fails 206 */ 207 @RequiresApi(34) 208 suspend fun prepareGetCredential( 209 request: GetCredentialRequest, 210 ): PrepareGetCredentialResponse = suspendCancellableCoroutine { continuation -> 211 // Any Android API that supports cancellation should be configured to propagate 212 // coroutine cancellation as follows: 213 val canceller = CancellationSignal() 214 continuation.invokeOnCancellation { canceller.cancel() } 215 216 val callback = 217 object : 218 CredentialManagerCallback<PrepareGetCredentialResponse, GetCredentialException> { 219 override fun onResult(result: PrepareGetCredentialResponse) { 220 if (continuation.isActive) { 221 continuation.resume(result) 222 } 223 } 224 225 override fun onError(e: GetCredentialException) { 226 if (continuation.isActive) { 227 continuation.resumeWithException(e) 228 } 229 } 230 } 231 232 prepareGetCredentialAsync( 233 request, 234 canceller, 235 // Use a direct executor to avoid extra dispatch. Resuming the continuation will 236 // handle getting to the right thread or pool via the ContinuationInterceptor. 237 Runnable::run, 238 callback 239 ) 240 } 241 242 /** 243 * Registers a user credential that can be used to authenticate the user to the app in the 244 * future. 245 * 246 * The execution potentially launches framework UI flows for a user to view their registration 247 * options, grant consent, etc. 248 * 249 * @param context the context used to launch any UI needed; use an activity context to make sure 250 * the UI will be launched within the same task stack 251 * @param request the request for creating the credential 252 * @throws CreateCredentialException If the request fails 253 */ 254 suspend fun createCredential( 255 context: Context, 256 request: CreateCredentialRequest, 257 ): CreateCredentialResponse = suspendCancellableCoroutine { continuation -> 258 // Any Android API that supports cancellation should be configured to propagate 259 // coroutine cancellation as follows: 260 val canceller = CancellationSignal() 261 continuation.invokeOnCancellation { canceller.cancel() } 262 263 val callback = 264 object : 265 CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> { 266 override fun onResult(result: CreateCredentialResponse) { 267 if (continuation.isActive) { 268 continuation.resume(result) 269 } 270 } 271 272 override fun onError(e: CreateCredentialException) { 273 if (continuation.isActive) { 274 continuation.resumeWithException(e) 275 } 276 } 277 } 278 279 createCredentialAsync( 280 context, 281 request, 282 canceller, 283 // Use a direct executor to avoid extra dispatch. Resuming the continuation will 284 // handle getting to the right thread or pool via the ContinuationInterceptor. 285 Runnable::run, 286 callback 287 ) 288 } 289 290 /** 291 * Clears the current user credential state from all credential providers. 292 * 293 * You should invoked this api after your user signs out of your app to notify all credential 294 * providers that any stored credential session for the given app should be cleared. 295 * 296 * A credential provider may have stored an active credential session and use it to limit 297 * sign-in options for future get-credential calls. For example, it may prioritize the active 298 * credential over any other available credential. When your user explicitly signs out of your 299 * app and in order to get the holistic sign-in options the next time, you should call this API 300 * to let the provider clear any stored credential session. 301 * 302 * If the API is called with [ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL] then 303 * any restore credential stored on device will be cleared. 304 * 305 * @param request the request for clearing the app user's credential state 306 * @throws ClearCredentialException If the request fails 307 */ 308 suspend fun clearCredentialState(request: ClearCredentialStateRequest): Unit = 309 suspendCancellableCoroutine { continuation -> 310 // Any Android API that supports cancellation should be configured to propagate 311 // coroutine cancellation as follows: 312 val canceller = CancellationSignal() 313 continuation.invokeOnCancellation { canceller.cancel() } 314 315 val callback = 316 object : CredentialManagerCallback<Void?, ClearCredentialException> { 317 override fun onResult(result: Void?) { 318 if (continuation.isActive) { 319 continuation.resume(Unit) 320 } 321 } 322 323 override fun onError(e: ClearCredentialException) { 324 if (continuation.isActive) { 325 continuation.resumeWithException(e) 326 } 327 } 328 } 329 330 clearCredentialStateAsync( 331 request, 332 canceller, 333 // Use a direct executor to avoid extra dispatch. Resuming the continuation will 334 // handle getting to the right thread or pool via the ContinuationInterceptor. 335 Runnable::run, 336 callback 337 ) 338 } 339 340 /** 341 * Requests a credential from the user. 342 * 343 * This API uses callbacks instead of Kotlin coroutines. 344 * 345 * The execution potentially launches framework UI flows for a user to view available 346 * credentials, consent to using one of them, etc. 347 * 348 * @param context the context used to launch any UI needed; use an activity context to make sure 349 * the UI will be launched within the same task stack 350 * @param request the request for getting the credential 351 * @param cancellationSignal an optional signal that allows for cancelling this call 352 * @param executor the callback will take place on this executor 353 * @param callback the callback invoked when the request succeeds or fails 354 */ 355 fun getCredentialAsync( 356 context: Context, 357 request: GetCredentialRequest, 358 cancellationSignal: CancellationSignal?, 359 executor: Executor, 360 callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>, 361 ) 362 363 /** 364 * Requests a credential from the user. 365 * 366 * This API uses callbacks instead of Kotlin coroutines. 367 * 368 * Different from the other `getCredentialAsync(GetCredentialRequest, Activity)` API, this API 369 * launches the remaining flows to retrieve an app credential from the user, after the completed 370 * prefetch work corresponding to the given `pendingGetCredentialHandle`. Use this API to 371 * complete the full credential retrieval operation after you initiated a request through the 372 * [prepareGetCredentialAsync] API. 373 * 374 * The execution can potentially launch UI flows to collect user consent to using a credential, 375 * display a picker when multiple credentials exist, etc. 376 * 377 * @param context the context used to launch any UI needed; use an activity context to make sure 378 * the UI will be launched within the same task stack 379 * @param pendingGetCredentialHandle the handle representing the pending operation to resume 380 * @param cancellationSignal an optional signal that allows for cancelling this call 381 * @param executor the callback will take place on this executor 382 * @param callback the callback invoked when the request succeeds or fails 383 */ 384 @RequiresApi(34) 385 fun getCredentialAsync( 386 context: Context, 387 pendingGetCredentialHandle: PrepareGetCredentialResponse.PendingGetCredentialHandle, 388 cancellationSignal: CancellationSignal?, 389 executor: Executor, 390 callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>, 391 ) 392 393 /** 394 * Prepares for a get-credential operation. Returns a [PrepareGetCredentialResponse] that can 395 * later be used to launch the credential retrieval UI flow to finalize a user credential for 396 * your app. 397 * 398 * This API uses callbacks instead of Kotlin coroutines. 399 * 400 * This API doesn't invoke any UI. It only performs the preparation work so that you can later 401 * launch the remaining get-credential operation (involves UIs) through the [getCredentialAsync] 402 * API which incurs less latency than executing the whole operation in one call. 403 * 404 * @param request the request for getting the credential 405 * @param cancellationSignal an optional signal that allows for cancelling this call 406 * @param executor the callback will take place on this executor 407 * @param callback the callback invoked when the request succeeds or fails 408 */ 409 @RequiresApi(34) 410 fun prepareGetCredentialAsync( 411 request: GetCredentialRequest, 412 cancellationSignal: CancellationSignal?, 413 executor: Executor, 414 callback: CredentialManagerCallback<PrepareGetCredentialResponse, GetCredentialException>, 415 ) 416 417 /** 418 * Registers a user credential that can be used to authenticate the user to the app in the 419 * future. 420 * 421 * This API uses callbacks instead of Kotlin coroutines. 422 * 423 * The execution potentially launches framework UI flows for a user to view their registration 424 * options, grant consent, etc. 425 * 426 * @param context the context used to launch any UI needed; use an activity context to make sure 427 * the UI will be launched within the same task stack 428 * @param request the request for creating the credential 429 * @param cancellationSignal an optional signal that allows for cancelling this call 430 * @param executor the callback will take place on this executor 431 * @param callback the callback invoked when the request succeeds or fails 432 */ 433 fun createCredentialAsync( 434 context: Context, 435 request: CreateCredentialRequest, 436 cancellationSignal: CancellationSignal?, 437 executor: Executor, 438 callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>, 439 ) 440 441 /** 442 * Clears the current user credential state from all credential providers. 443 * 444 * This API uses callbacks instead of Kotlin coroutines. 445 * 446 * You should invoked this api after your user signs out of your app to notify all credential 447 * providers that any stored credential session for the given app should be cleared. 448 * 449 * A credential provider may have stored an active credential session and use it to limit 450 * sign-in options for future get-credential calls. For example, it may prioritize the active 451 * credential over any other available credential. When your user explicitly signs out of your 452 * app and in order to get the holistic sign-in options the next time, you should call this API 453 * to let the provider clear any stored credential session. 454 * 455 * @param request the request for clearing the app user's credential state 456 * @param cancellationSignal an optional signal that allows for cancelling this call 457 * @param executor the callback will take place on this executor 458 * @param callback the callback invoked when the request succeeds or fails 459 */ 460 fun clearCredentialStateAsync( 461 request: ClearCredentialStateRequest, 462 cancellationSignal: CancellationSignal?, 463 executor: Executor, 464 callback: CredentialManagerCallback<Void?, ClearCredentialException>, 465 ) 466 467 /** 468 * Returns a pending intent that shows a screen that lets a user enable a Credential Manager 469 * provider. 470 * 471 * @return the pending intent that can be launched 472 */ 473 @RequiresApi(34) fun createSettingsPendingIntent(): PendingIntent 474 } 475