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