1 /*
2  * 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 android.credentials;
18 
19 import static java.util.Objects.requireNonNull;
20 
21 import android.annotation.CallbackExecutor;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SystemService;
27 import android.annotation.TestApi;
28 import android.app.PendingIntent;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.IntentSender;
32 import android.os.Binder;
33 import android.os.CancellationSignal;
34 import android.os.ICancellationSignal;
35 import android.os.OutcomeReceiver;
36 import android.os.RemoteException;
37 import android.provider.DeviceConfig;
38 import android.util.Log;
39 
40 import java.lang.annotation.Retention;
41 import java.lang.annotation.RetentionPolicy;
42 import java.util.List;
43 import java.util.concurrent.Executor;
44 
45 /**
46  * Manages user authentication flows.
47  *
48  * <p>Note that an application should call the Jetpack CredentialManager apis instead of directly
49  * calling these framework apis.
50  *
51  * <p>The CredentialManager apis launch framework UI flows for a user to register a new credential
52  * or to consent to a saved credential from supported credential providers, which can then be used
53  * to authenticate to the app.
54  */
55 @SystemService(Context.CREDENTIAL_SERVICE)
56 public final class CredentialManager {
57     private static final String TAG = "CredentialManager";
58 
59     /** @hide */
60     @IntDef(
61             flag = true,
62             prefix = {"PROVIDER_FILTER_"},
63             value = {
64                 PROVIDER_FILTER_ALL_PROVIDERS,
65                 PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY,
66                 PROVIDER_FILTER_USER_PROVIDERS_ONLY,
67             })
68     @Retention(RetentionPolicy.SOURCE)
69     public @interface ProviderFilter {}
70 
71     /**
72      * Returns both system and user credential providers.
73      *
74      * @hide
75      */
76     @TestApi public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0;
77 
78     /**
79      * Returns system credential providers only.
80      *
81      * @hide
82      */
83     @TestApi public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1;
84 
85     /**
86      * Returns user credential providers only.
87      *
88      * @hide
89      */
90     @TestApi public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2;
91 
92     private final Context mContext;
93     private final ICredentialManager mService;
94 
95     /**
96      * Flag to enable and disable Credential Manager.
97      *
98      * @hide
99      */
100     public static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER =
101             "enable_credential_manager";
102 
103     /**
104      * Flag to enable and disable Credential Description api.
105      *
106      * @hide
107      */
108     private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
109             "enable_credential_description_api";
110 
111     /**
112      * @hide instantiated by ContextImpl.
113      */
CredentialManager(Context context, ICredentialManager service)114     public CredentialManager(Context context, ICredentialManager service) {
115         mContext = context;
116         mService = service;
117     }
118 
119     /**
120      * Launches the necessary flows to retrieve an app credential from the user.
121      *
122      * <p>The execution can potentially launch UI flows to collect user consent to using a
123      * credential, display a picker when multiple credentials exist, etc.
124      * Callers (e.g. browsers) may optionally set origin in {@link GetCredentialRequest} for an
125      * app different from their own, to be able to get credentials on behalf of that app. They would
126      * need additional permission {@code CREDENTIAL_MANAGER_SET_ORIGIN}
127      * to use this functionality
128      *
129      * @param context the context used to launch any UI needed; use an activity context to make sure
130      *                the UI will be launched within the same task stack
131      * @param request the request specifying type(s) of credentials to get from the user
132      * @param cancellationSignal an optional signal that allows for cancelling this call
133      * @param executor the callback will take place on this {@link Executor}
134      * @param callback the callback invoked when the request succeeds or fails
135      */
getCredential( @onNull Context context, @NonNull GetCredentialRequest request, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback)136     public void getCredential(
137             @NonNull Context context,
138             @NonNull GetCredentialRequest request,
139             @Nullable CancellationSignal cancellationSignal,
140             @CallbackExecutor @NonNull Executor executor,
141             @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
142         requireNonNull(request, "request must not be null");
143         requireNonNull(context, "context must not be null");
144         requireNonNull(executor, "executor must not be null");
145         requireNonNull(callback, "callback must not be null");
146 
147         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
148             Log.w(TAG, "getCredential already canceled");
149             return;
150         }
151 
152         ICancellationSignal cancelRemote = null;
153         try {
154             cancelRemote =
155                     mService.executeGetCredential(
156                             request,
157                             new GetCredentialTransport(context, executor, callback),
158                             mContext.getOpPackageName());
159         } catch (RemoteException e) {
160             e.rethrowFromSystemServer();
161         }
162 
163         if (cancellationSignal != null && cancelRemote != null) {
164             cancellationSignal.setRemote(cancelRemote);
165         }
166     }
167 
168     /**
169      * Launches the remaining flows to retrieve an app credential from the user, after the
170      * completed prefetch work corresponding to the given {@code pendingGetCredentialHandle}.
171      *
172      * <p>The execution can potentially launch UI flows to collect user consent to using a
173      * credential, display a picker when multiple credentials exist, etc.
174      *
175      * <p>Use this API to complete the full credential retrieval operation after you initiated a
176      * request through the {@link #prepareGetCredential(
177      * GetCredentialRequest, CancellationSignal, Executor, OutcomeReceiver)} API.
178      *
179      * @param context the context used to launch any UI needed; use an activity context to make sure
180      *                the UI will be launched within the same task stack
181      * @param pendingGetCredentialHandle the handle representing the pending operation to resume
182      * @param cancellationSignal an optional signal that allows for cancelling this call
183      * @param executor the callback will take place on this {@link Executor}
184      * @param callback the callback invoked when the request succeeds or fails
185      */
getCredential( @onNull Context context, @NonNull PrepareGetCredentialResponse.PendingGetCredentialHandle pendingGetCredentialHandle, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback)186     public void getCredential(
187             @NonNull Context context,
188             @NonNull PrepareGetCredentialResponse.PendingGetCredentialHandle
189             pendingGetCredentialHandle,
190             @Nullable CancellationSignal cancellationSignal,
191             @CallbackExecutor @NonNull Executor executor,
192             @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
193         requireNonNull(pendingGetCredentialHandle, "pendingGetCredentialHandle must not be null");
194         requireNonNull(context, "context must not be null");
195         requireNonNull(executor, "executor must not be null");
196         requireNonNull(callback, "callback must not be null");
197 
198         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
199             Log.w(TAG, "getCredential already canceled");
200             return;
201         }
202 
203         pendingGetCredentialHandle.show(context, cancellationSignal, executor, callback);
204     }
205 
206     /**
207      * Prepare for a get-credential operation. Returns a {@link PrepareGetCredentialResponse} that
208      * can launch the credential retrieval UI flow to request a user credential for your app.
209      *
210      * <p>This API doesn't invoke any UI. It only performs the preparation work so that you can
211      * later launch the remaining get-credential operation (involves UIs) through the {@link
212      * #getCredential(Context, PrepareGetCredentialResponse.PendingGetCredentialHandle,
213      * CancellationSignal, Executor, OutcomeReceiver)} API which incurs less latency compared to
214      * the {@link #getCredential(Context, GetCredentialRequest, CancellationSignal, Executor,
215      * OutcomeReceiver)} API that executes the whole operation in one call.
216      *
217      * @param request            the request specifying type(s) of credentials to get from the user
218      * @param cancellationSignal an optional signal that allows for cancelling this call
219      * @param executor           the callback will take place on this {@link Executor}
220      * @param callback           the callback invoked when the request succeeds or fails
221      */
prepareGetCredential( @onNull GetCredentialRequest request, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver< PrepareGetCredentialResponse, GetCredentialException> callback)222     public void prepareGetCredential(
223             @NonNull GetCredentialRequest request,
224             @Nullable CancellationSignal cancellationSignal,
225             @CallbackExecutor @NonNull Executor executor,
226             @NonNull OutcomeReceiver<
227                     PrepareGetCredentialResponse, GetCredentialException> callback) {
228         requireNonNull(request, "request must not be null");
229         requireNonNull(executor, "executor must not be null");
230         requireNonNull(callback, "callback must not be null");
231 
232         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
233             Log.w(TAG, "prepareGetCredential already canceled");
234             return;
235         }
236 
237         ICancellationSignal cancelRemote = null;
238         GetCredentialTransportPendingUseCase getCredentialTransport =
239                 new GetCredentialTransportPendingUseCase();
240         try {
241             cancelRemote =
242                     mService.executePrepareGetCredential(
243                             request,
244                             new PrepareGetCredentialTransport(
245                                     executor, callback, getCredentialTransport),
246                             getCredentialTransport,
247                             mContext.getOpPackageName());
248         } catch (RemoteException e) {
249             e.rethrowFromSystemServer();
250         }
251 
252         if (cancellationSignal != null && cancelRemote != null) {
253             cancellationSignal.setRemote(cancelRemote);
254         }
255     }
256 
257     /**
258      * Launches the necessary flows to register an app credential for the user.
259      *
260      * <p>The execution can potentially launch UI flows to collect user consent to creating or
261      * storing the new credential, etc.
262      * Callers (e.g. browsers) may optionally set origin in {@link CreateCredentialRequest} for an
263      * app different from their own, to be able to get credentials on behalf of that app. They would
264      * need additional permission {@code CREDENTIAL_MANAGER_SET_ORIGIN}
265      * to use this functionality
266      *
267      * @param context the context used to launch any UI needed; use an activity context to make sure
268      *                the UI will be launched within the same task stack
269      * @param request the request specifying type(s) of credentials to get from the user
270      * @param cancellationSignal an optional signal that allows for cancelling this call
271      * @param executor the callback will take place on this {@link Executor}
272      * @param callback the callback invoked when the request succeeds or fails
273      */
createCredential( @onNull Context context, @NonNull CreateCredentialRequest request, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback)274     public void createCredential(
275             @NonNull Context context,
276             @NonNull CreateCredentialRequest request,
277             @Nullable CancellationSignal cancellationSignal,
278             @CallbackExecutor @NonNull Executor executor,
279             @NonNull
280                     OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
281         requireNonNull(request, "request must not be null");
282         requireNonNull(context, "context must not be null");
283         requireNonNull(executor, "executor must not be null");
284         requireNonNull(callback, "callback must not be null");
285 
286         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
287             Log.w(TAG, "createCredential already canceled");
288             return;
289         }
290 
291         ICancellationSignal cancelRemote = null;
292         try {
293             cancelRemote =
294                     mService.executeCreateCredential(
295                             request,
296                             new CreateCredentialTransport(context, executor, callback),
297                             mContext.getOpPackageName());
298         } catch (RemoteException e) {
299             e.rethrowFromSystemServer();
300         }
301 
302         if (cancellationSignal != null && cancelRemote != null) {
303             cancellationSignal.setRemote(cancelRemote);
304         }
305     }
306 
307     /**
308      * Clears the current user credential state from all credential providers.
309      *
310      * <p>You should invoked this api after your user signs out of your app to notify all credential
311      * providers that any stored credential session for the given app should be cleared.
312      *
313      * <p>A credential provider may have stored an active credential session and use it to limit
314      * sign-in options for future get-credential calls. For example, it may prioritize the active
315      * credential over any other available credential. When your user explicitly signs out of your
316      * app and in order to get the holistic sign-in options the next time, you should call this API
317      * to let the provider clear any stored credential session.
318      *
319      * @param request the request data
320      * @param cancellationSignal an optional signal that allows for cancelling this call
321      * @param executor the callback will take place on this {@link Executor}
322      * @param callback the callback invoked when the request succeeds or fails
323      */
clearCredentialState( @onNull ClearCredentialStateRequest request, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver<Void, ClearCredentialStateException> callback)324     public void clearCredentialState(
325             @NonNull ClearCredentialStateRequest request,
326             @Nullable CancellationSignal cancellationSignal,
327             @CallbackExecutor @NonNull Executor executor,
328             @NonNull OutcomeReceiver<Void, ClearCredentialStateException> callback) {
329         requireNonNull(request, "request must not be null");
330         requireNonNull(executor, "executor must not be null");
331         requireNonNull(callback, "callback must not be null");
332 
333         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
334             Log.w(TAG, "clearCredentialState already canceled");
335             return;
336         }
337 
338         ICancellationSignal cancelRemote = null;
339         try {
340             cancelRemote =
341                     mService.clearCredentialState(
342                             request,
343                             new ClearCredentialStateTransport(executor, callback),
344                             mContext.getOpPackageName());
345         } catch (RemoteException e) {
346             e.rethrowFromSystemServer();
347         }
348 
349         if (cancellationSignal != null && cancelRemote != null) {
350             cancellationSignal.setRemote(cancelRemote);
351         }
352     }
353 
354     /**
355      * Sets a list of all user configurable credential providers registered on the system. This API
356      * is intended for settings apps.
357      *
358      * @param primaryProviders the primary providers that user selected for saving credentials. In
359      *                         the most case, there should be only one primary provider, However,
360      *                         if there are more than one CredentialProviderService in the same APK,
361      *                         they should be passed in altogether.
362      * @param providers the list of enabled providers.
363      * @param userId the user ID to configure credential manager for
364      * @param executor the callback will take place on this {@link Executor}
365      * @param callback the callback invoked when the request succeeds or fails
366      * @hide
367      */
368     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
setEnabledProviders( @onNull List<String> primaryProviders, @NonNull List<String> providers, int userId, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver<Void, SetEnabledProvidersException> callback)369     public void setEnabledProviders(
370             @NonNull List<String> primaryProviders,
371             @NonNull List<String> providers,
372             int userId,
373             @CallbackExecutor @NonNull Executor executor,
374             @NonNull OutcomeReceiver<Void, SetEnabledProvidersException> callback) {
375         requireNonNull(executor, "executor must not be null");
376         requireNonNull(callback, "callback must not be null");
377         requireNonNull(providers, "providers must not be null");
378         requireNonNull(primaryProviders, "primaryProviders must not be null");
379 
380         try {
381             mService.setEnabledProviders(
382                     primaryProviders,
383                     providers, userId, new SetEnabledProvidersTransport(executor, callback));
384         } catch (RemoteException e) {
385             e.rethrowFromSystemServer();
386         }
387     }
388 
389     /**
390      * Returns {@code true} if the calling application provides a CredentialProviderService that is
391      * enabled for the current user, or {@code false} otherwise. CredentialProviderServices are
392      * enabled on a per-service basis so the individual component name of the service should be
393      * passed in here.
394      *
395      * @param componentName the component name to check is enabled
396      */
isEnabledCredentialProviderService(@onNull ComponentName componentName)397     public boolean isEnabledCredentialProviderService(@NonNull ComponentName componentName) {
398         requireNonNull(componentName, "componentName must not be null");
399 
400         try {
401             return mService.isEnabledCredentialProviderService(
402                     componentName, mContext.getOpPackageName());
403         } catch (RemoteException e) {
404             throw e.rethrowFromSystemServer();
405         }
406     }
407 
408     /**
409      * Returns the list of CredentialProviderInfo for all discovered credential providers on this
410      * device but will include test system providers as well.
411      *
412      * @hide
413      */
414     @NonNull
415     @TestApi
416     @RequiresPermission(
417       anyOf = {
418         android.Manifest.permission.QUERY_ALL_PACKAGES,
419         android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS
420       })
getCredentialProviderServicesForTesting( @roviderFilter int providerFilter)421     public List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
422              @ProviderFilter int providerFilter) {
423         try {
424             return mService.getCredentialProviderServicesForTesting(providerFilter);
425         } catch (RemoteException e) {
426             throw e.rethrowFromSystemServer();
427         }
428     }
429 
430     /**
431      * Returns the list of CredentialProviderInfo for all discovered credential providers on this
432      * device.
433      *
434      * @hide
435      */
436     @NonNull
437     @RequiresPermission(
438       anyOf = {
439         android.Manifest.permission.QUERY_ALL_PACKAGES,
440         android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS
441       })
getCredentialProviderServices( int userId, @ProviderFilter int providerFilter)442     public List<CredentialProviderInfo> getCredentialProviderServices(
443             int userId, @ProviderFilter int providerFilter) {
444         try {
445             return mService.getCredentialProviderServices(userId, providerFilter);
446         } catch (RemoteException e) {
447             throw e.rethrowFromSystemServer();
448         }
449     }
450 
451     /**
452      * Returns whether the service is enabled.
453      *
454      * @hide
455      */
456     @TestApi
isServiceEnabled(@onNull Context context)457     public static boolean isServiceEnabled(@NonNull Context context) {
458         requireNonNull(context, "context must not be null");
459         if (context == null) {
460             return false;
461         }
462         CredentialManager credentialManager =
463                 (CredentialManager) context.getSystemService(Context.CREDENTIAL_SERVICE);
464         if (credentialManager != null) {
465             return credentialManager.isServiceEnabled();
466         }
467         return false;
468     }
469 
470     /**
471      * Returns whether the service is enabled.
472      *
473      * @hide
474      */
isServiceEnabled()475     private boolean isServiceEnabled() {
476         try {
477             return mService.isServiceEnabled();
478         } catch (RemoteException e) {
479             return false;
480         }
481     }
482 
483     /**
484      * Returns whether the credential description api is enabled.
485      *
486      * @hide
487      */
isCredentialDescriptionApiEnabled(Context context)488     public static boolean isCredentialDescriptionApiEnabled(Context context) {
489         if (context == null) {
490             return false;
491         }
492         CredentialManager credentialManager =
493                 (CredentialManager) context.getSystemService(Context.CREDENTIAL_SERVICE);
494         if (credentialManager != null) {
495             return credentialManager.isCredentialDescriptionApiEnabled();
496         }
497         return false;
498     }
499 
isCredentialDescriptionApiEnabled()500     private boolean isCredentialDescriptionApiEnabled() {
501         return DeviceConfig.getBoolean(
502                 DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
503     }
504 
505     /**
506      * Registers a {@link CredentialDescription} for an actively provisioned {@link Credential} a
507      * CredentialProvider has. This registry will then be used to determine where to fetch the
508      * requested {@link Credential} from. Not all credential types will be supported. The
509      * distinction will be made by the JetPack layer. For the types that are supported, JetPack will
510      * add a new key-value pair into {@link GetCredentialRequest}. These will not be persistent on
511      * the device. The Credential Providers will need to call this API again upon device reboot.
512      *
513      * @param request the request data
514      * @throws {@link UnsupportedOperationException} if the feature has not been enabled.
515      * @throws {@link com.android.server.credentials.NonCredentialProviderCallerException} if the
516      *     calling package name is not also listed as a Credential Provider.
517      * @throws {@link IllegalArgumentException} if the calling Credential Provider can not handle
518      *     one or more of the Credential Types that are sent for registration.
519      */
registerCredentialDescription( @onNull RegisterCredentialDescriptionRequest request)520     public void registerCredentialDescription(
521             @NonNull RegisterCredentialDescriptionRequest request) {
522         requireNonNull(request, "request must not be null");
523 
524         try {
525             mService.registerCredentialDescription(request, mContext.getOpPackageName());
526         } catch (RemoteException e) {
527             e.rethrowFromSystemServer();
528         }
529     }
530 
531     /**
532      * Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential}
533      * that has been registered previously.
534      *
535      * @param request the request data
536      * @throws {@link UnsupportedOperationException} if the feature has not been enabled.
537      */
unregisterCredentialDescription( @onNull UnregisterCredentialDescriptionRequest request)538     public void unregisterCredentialDescription(
539             @NonNull UnregisterCredentialDescriptionRequest request) {
540         requireNonNull(request, "request must not be null");
541 
542         try {
543             mService.unregisterCredentialDescription(request, mContext.getOpPackageName());
544         } catch (RemoteException e) {
545             e.rethrowFromSystemServer();
546         }
547     }
548 
549     private static class PrepareGetCredentialTransport extends IPrepareGetCredentialCallback.Stub {
550         // TODO: listen for cancellation to release callback.
551 
552         private final Executor mExecutor;
553         private final OutcomeReceiver<
554                 PrepareGetCredentialResponse, GetCredentialException> mCallback;
555         private final GetCredentialTransportPendingUseCase mGetCredentialTransport;
556 
PrepareGetCredentialTransport( Executor executor, OutcomeReceiver<PrepareGetCredentialResponse, GetCredentialException> callback, GetCredentialTransportPendingUseCase getCredentialTransport)557         private PrepareGetCredentialTransport(
558                 Executor executor,
559                 OutcomeReceiver<PrepareGetCredentialResponse, GetCredentialException> callback,
560                 GetCredentialTransportPendingUseCase getCredentialTransport) {
561             mExecutor = executor;
562             mCallback = callback;
563             mGetCredentialTransport = getCredentialTransport;
564         }
565 
566         @Override
onResponse(PrepareGetCredentialResponseInternal response)567         public void onResponse(PrepareGetCredentialResponseInternal response) {
568             final long identity = Binder.clearCallingIdentity();
569             try {
570                 mExecutor.execute(() -> mCallback.onResult(
571                         new PrepareGetCredentialResponse(response, mGetCredentialTransport)));
572             } finally {
573                 Binder.restoreCallingIdentity(identity);
574             }
575         }
576 
577         @Override
onError(String errorType, String message)578         public void onError(String errorType, String message) {
579             final long identity = Binder.clearCallingIdentity();
580             try {
581                 mExecutor.execute(
582                         () -> mCallback.onError(new GetCredentialException(errorType, message)));
583             }  finally {
584                 Binder.restoreCallingIdentity(identity);
585             }
586         }
587     }
588 
589     /** @hide */
590     protected static class GetCredentialTransportPendingUseCase
591             extends IGetCredentialCallback.Stub {
592         @Nullable private PrepareGetCredentialResponse.GetPendingCredentialInternalCallback
593                 mCallback = null;
594 
GetCredentialTransportPendingUseCase()595         private GetCredentialTransportPendingUseCase() {}
596 
setCallback( PrepareGetCredentialResponse.GetPendingCredentialInternalCallback callback)597         public void setCallback(
598                 PrepareGetCredentialResponse.GetPendingCredentialInternalCallback callback) {
599             if (mCallback == null) {
600                 mCallback = callback;
601             } else {
602                 throw new IllegalStateException("callback has already been set once");
603             }
604         }
605 
606         @Override
onPendingIntent(PendingIntent pendingIntent)607         public void onPendingIntent(PendingIntent pendingIntent) {
608             if (mCallback != null) {
609                 mCallback.onPendingIntent(pendingIntent);
610             } else {
611                 Log.d(TAG, "Unexpected onPendingIntent call before the show invocation");
612             }
613         }
614 
615         @Override
onResponse(GetCredentialResponse response)616         public void onResponse(GetCredentialResponse response) {
617             if (mCallback != null) {
618                 final long identity = Binder.clearCallingIdentity();
619                 try {
620                     mCallback.onResponse(response);
621                 } finally {
622                     Binder.restoreCallingIdentity(identity);
623                 }
624             } else {
625                 Log.d(TAG, "Unexpected onResponse call before the show invocation");
626             }
627         }
628 
629         @Override
onError(String errorType, String message)630         public void onError(String errorType, String message) {
631             if (mCallback != null) {
632                 final long identity = Binder.clearCallingIdentity();
633                 try {
634                     mCallback.onError(errorType, message);
635                 } finally {
636                     Binder.restoreCallingIdentity(identity);
637                 }
638             } else {
639                 Log.d(TAG, "Unexpected onError call before the show invocation");
640             }
641         }
642     }
643 
644     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
645         // TODO: listen for cancellation to release callback.
646 
647         private final Context mContext;
648         private final Executor mExecutor;
649         private final OutcomeReceiver<GetCredentialResponse, GetCredentialException> mCallback;
650 
GetCredentialTransport( Context context, Executor executor, OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback)651         private GetCredentialTransport(
652                 Context context,
653                 Executor executor,
654                 OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
655             mContext = context;
656             mExecutor = executor;
657             mCallback = callback;
658         }
659 
660         @Override
onPendingIntent(PendingIntent pendingIntent)661         public void onPendingIntent(PendingIntent pendingIntent) {
662             try {
663                 mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
664             } catch (IntentSender.SendIntentException e) {
665                 Log.e(
666                         TAG,
667                         "startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
668                         e);
669                 final long identity = Binder.clearCallingIdentity();
670                 try {
671                     mExecutor.execute(() -> mCallback.onError(
672                             new GetCredentialException(GetCredentialException.TYPE_UNKNOWN)));
673                 } finally {
674                     Binder.restoreCallingIdentity(identity);
675                 }
676             }
677         }
678 
679         @Override
onResponse(GetCredentialResponse response)680         public void onResponse(GetCredentialResponse response) {
681             final long identity = Binder.clearCallingIdentity();
682             try {
683                 mExecutor.execute(() -> mCallback.onResult(response));
684             } finally {
685                 Binder.restoreCallingIdentity(identity);
686             }
687         }
688 
689         @Override
onError(String errorType, String message)690         public void onError(String errorType, String message) {
691             final long identity = Binder.clearCallingIdentity();
692             try {
693                 mExecutor.execute(
694                         () -> mCallback.onError(new GetCredentialException(errorType, message)));
695             } finally {
696                 Binder.restoreCallingIdentity(identity);
697             }
698         }
699     }
700 
701     private static class CreateCredentialTransport extends ICreateCredentialCallback.Stub {
702         // TODO: listen for cancellation to release callback.
703 
704         private final Context mContext;
705         private final Executor mExecutor;
706         private final OutcomeReceiver<CreateCredentialResponse, CreateCredentialException>
707                 mCallback;
708 
CreateCredentialTransport( Context context, Executor executor, OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback)709         private CreateCredentialTransport(
710                 Context context,
711                 Executor executor,
712                 OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
713             mContext = context;
714             mExecutor = executor;
715             mCallback = callback;
716         }
717 
718         @Override
onPendingIntent(PendingIntent pendingIntent)719         public void onPendingIntent(PendingIntent pendingIntent) {
720             try {
721                 mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
722             } catch (IntentSender.SendIntentException e) {
723                 Log.e(
724                         TAG,
725                         "startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
726                         e);
727                 final long identity = Binder.clearCallingIdentity();
728                 try {
729                     mExecutor.execute(() -> mCallback.onError(
730                             new CreateCredentialException(CreateCredentialException.TYPE_UNKNOWN)));
731                 } finally {
732                     Binder.restoreCallingIdentity(identity);
733                 }
734             }
735         }
736 
737         @Override
onResponse(CreateCredentialResponse response)738         public void onResponse(CreateCredentialResponse response) {
739             final long identity = Binder.clearCallingIdentity();
740             try {
741                 mExecutor.execute(() -> mCallback.onResult(response));
742             } finally {
743                 Binder.restoreCallingIdentity(identity);
744             }
745         }
746 
747         @Override
onError(String errorType, String message)748         public void onError(String errorType, String message) {
749             final long identity = Binder.clearCallingIdentity();
750             try {
751                 mExecutor.execute(
752                         () -> mCallback.onError(new CreateCredentialException(errorType, message)));
753             } finally {
754                 Binder.restoreCallingIdentity(identity);
755             }
756         }
757     }
758 
759     private static class ClearCredentialStateTransport extends IClearCredentialStateCallback.Stub {
760         // TODO: listen for cancellation to release callback.
761 
762         private final Executor mExecutor;
763         private final OutcomeReceiver<Void, ClearCredentialStateException> mCallback;
764 
ClearCredentialStateTransport( Executor executor, OutcomeReceiver<Void, ClearCredentialStateException> callback)765         private ClearCredentialStateTransport(
766                 Executor executor, OutcomeReceiver<Void, ClearCredentialStateException> callback) {
767             mExecutor = executor;
768             mCallback = callback;
769         }
770 
771         @Override
onSuccess()772         public void onSuccess() {
773             final long identity = Binder.clearCallingIdentity();
774             try {
775                 mCallback.onResult(null);
776             } finally {
777                 Binder.restoreCallingIdentity(identity);
778             }
779         }
780 
781         @Override
onError(String errorType, String message)782         public void onError(String errorType, String message) {
783             final long identity = Binder.clearCallingIdentity();
784             try {
785                 mExecutor.execute(
786                         () -> mCallback.onError(
787                                 new ClearCredentialStateException(errorType, message)));
788             } finally {
789                 Binder.restoreCallingIdentity(identity);
790             }
791         }
792     }
793 
794     private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub {
795         // TODO: listen for cancellation to release callback.
796 
797         private final Executor mExecutor;
798         private final OutcomeReceiver<Void, SetEnabledProvidersException> mCallback;
799 
SetEnabledProvidersTransport( Executor executor, OutcomeReceiver<Void, SetEnabledProvidersException> callback)800         private SetEnabledProvidersTransport(
801                 Executor executor, OutcomeReceiver<Void, SetEnabledProvidersException> callback) {
802             mExecutor = executor;
803             mCallback = callback;
804         }
805 
onResponse(Void result)806         public void onResponse(Void result) {
807             final long identity = Binder.clearCallingIdentity();
808             try {
809                 mExecutor.execute(() -> mCallback.onResult(result));
810             } finally {
811                 Binder.restoreCallingIdentity(identity);
812             }
813         }
814 
815         @Override
onResponse()816         public void onResponse() {
817             final long identity = Binder.clearCallingIdentity();
818             try {
819                 mExecutor.execute(() -> mCallback.onResult(null));
820             } finally {
821                 Binder.restoreCallingIdentity(identity);
822             }
823         }
824 
825         @Override
onError(String errorType, String message)826         public void onError(String errorType, String message) {
827             final long identity = Binder.clearCallingIdentity();
828             try {
829                 mExecutor.execute(
830                         () -> mCallback.onError(
831                                 new SetEnabledProvidersException(errorType, message)));
832             } finally {
833                 Binder.restoreCallingIdentity(identity);
834             }
835         }
836     }
837 }
838