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