/* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.credentials; import static java.util.Objects.requireNonNull; import android.annotation.CallbackExecutor; import android.annotation.Hide; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.ActivityOptions; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.IntentSender; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.IBinder; import android.os.ICancellationSignal; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.provider.DeviceConfig; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.concurrent.Executor; /** * Manages user authentication flows. * *

Note that an application should call the Jetpack CredentialManager apis instead of directly * calling these framework apis. * *

The CredentialManager apis launch framework UI flows for a user to register a new credential * or to consent to a saved credential from supported credential providers, which can then be used * to authenticate to the app. */ @SystemService(Context.CREDENTIAL_SERVICE) @RequiresFeature(PackageManager.FEATURE_CREDENTIALS) public final class CredentialManager { /** @hide **/ @Hide public static final String TAG = "CredentialManager"; private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic() .setPendingIntentBackgroundActivityStartMode( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle(); /** @hide */ @IntDef( flag = true, prefix = {"PROVIDER_FILTER_"}, value = { PROVIDER_FILTER_ALL_PROVIDERS, PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY, PROVIDER_FILTER_USER_PROVIDERS_ONLY, // By default the returned list of providers will not include any providers that // have been hidden by device policy. However, there are some cases where we want // them to show up (e.g. settings) so this will return the list of providers with // the hidden ones included. PROVIDER_FILTER_USER_PROVIDERS_INCLUDING_HIDDEN, }) @Retention(RetentionPolicy.SOURCE) public @interface ProviderFilter {} /** * Returns both system and user credential providers. * * @hide */ @TestApi public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0; /** * Returns system credential providers only. * * @hide */ @TestApi public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1; /** * Returns user credential providers only. * * @hide */ @TestApi public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2; /** * Returns user credential providers only. This will include providers that * have been disabled by the device policy. * * @hide */ public static final int PROVIDER_FILTER_USER_PROVIDERS_INCLUDING_HIDDEN = 3; private final Context mContext; private final ICredentialManager mService; /** * Flag to enable and disable Credential Manager. * * @hide */ public static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER = "enable_credential_manager"; /** * Flag to enable and disable Credential Description api. * * @hide */ private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API = "enable_credential_description_api"; /** * @hide */ @Hide public static final String EXTRA_AUTOFILL_RESULT_RECEIVER = "android.credentials.AUTOFILL_RESULT_RECEIVER"; /** * @hide instantiated by ContextImpl. */ public CredentialManager(Context context, ICredentialManager service) { mContext = context; mService = service; } /** * Returns a list of candidate credentials returned from credential manager providers * * @param request the request specifying type(s) of credentials to get from the * credential providers * @param cancellationSignal an optional signal that allows for cancelling this call * @param executor the callback will take place on this {@link Executor} * @param callback the callback invoked when the request succeeds or fails * * @hide */ @Hide public void getCandidateCredentials( @NonNull GetCredentialRequest request, @NonNull String callingPackage, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver callback, @NonNull IBinder clientCallback ) { requireNonNull(request, "request must not be null"); requireNonNull(callingPackage, "callingPackage must not be null"); requireNonNull(executor, "executor must not be null"); requireNonNull(callback, "callback must not be null"); if (cancellationSignal != null && cancellationSignal.isCanceled()) { Log.w(TAG, "getCandidateCredentials already canceled"); return; } ICancellationSignal cancelRemote = null; try { cancelRemote = mService.getCandidateCredentials( request, new GetCandidateCredentialsTransport(executor, callback), clientCallback, callingPackage); } catch (RemoteException e) { e.rethrowFromSystemServer(); } if (cancellationSignal != null && cancelRemote != null) { cancellationSignal.setRemote(cancelRemote); } } /** * Launches the necessary flows to retrieve an app credential from the user. * *

The execution can potentially launch UI flows to collect user consent to using a * credential, display a picker when multiple credentials exist, etc. * Callers (e.g. browsers) may optionally set origin in {@link GetCredentialRequest} for an * app different from their own, to be able to get credentials on behalf of that app. They would * need additional permission {@code CREDENTIAL_MANAGER_SET_ORIGIN} * to use this functionality * * @param context the context used to launch any UI needed; use an activity context to make sure * the UI will be launched within the same task stack * @param request the request specifying type(s) of credentials to get from the user * @param cancellationSignal an optional signal that allows for cancelling this call * @param executor the callback will take place on this {@link Executor} * @param callback the callback invoked when the request succeeds or fails */ public void getCredential( @NonNull Context context, @NonNull GetCredentialRequest request, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver callback) { requireNonNull(request, "request must not be null"); requireNonNull(context, "context must not be null"); requireNonNull(executor, "executor must not be null"); requireNonNull(callback, "callback must not be null"); if (cancellationSignal != null && cancellationSignal.isCanceled()) { Log.w(TAG, "getCredential already canceled"); return; } ICancellationSignal cancelRemote = null; try { cancelRemote = mService.executeGetCredential( request, new GetCredentialTransport(context, executor, callback), mContext.getOpPackageName()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } if (cancellationSignal != null && cancelRemote != null) { cancellationSignal.setRemote(cancelRemote); } } /** * Launches the remaining flows to retrieve an app credential from the user, after the * completed prefetch work corresponding to the given {@code pendingGetCredentialHandle}. * *

The execution can potentially launch UI flows to collect user consent to using a * credential, display a picker when multiple credentials exist, etc. * *

Use this API to complete the full credential retrieval operation after you initiated a * request through the {@link #prepareGetCredential( * GetCredentialRequest, CancellationSignal, Executor, OutcomeReceiver)} API. * * @param context the context used to launch any UI needed; use an activity context to make sure * the UI will be launched within the same task stack * @param pendingGetCredentialHandle the handle representing the pending operation to resume * @param cancellationSignal an optional signal that allows for cancelling this call * @param executor the callback will take place on this {@link Executor} * @param callback the callback invoked when the request succeeds or fails */ public void getCredential( @NonNull Context context, @NonNull PrepareGetCredentialResponse.PendingGetCredentialHandle pendingGetCredentialHandle, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver callback) { requireNonNull(pendingGetCredentialHandle, "pendingGetCredentialHandle must not be null"); requireNonNull(context, "context must not be null"); requireNonNull(executor, "executor must not be null"); requireNonNull(callback, "callback must not be null"); if (cancellationSignal != null && cancellationSignal.isCanceled()) { Log.w(TAG, "getCredential already canceled"); return; } pendingGetCredentialHandle.show(context, cancellationSignal, executor, callback); } /** * Prepare for a get-credential operation. Returns a {@link PrepareGetCredentialResponse} that * can launch the credential retrieval UI flow to request a user credential for your app. * *

This API doesn't invoke any UI. It only performs the preparation work so that you can * later launch the remaining get-credential operation (involves UIs) through the {@link * #getCredential(Context, PrepareGetCredentialResponse.PendingGetCredentialHandle, * CancellationSignal, Executor, OutcomeReceiver)} API which incurs less latency compared to * the {@link #getCredential(Context, GetCredentialRequest, CancellationSignal, Executor, * OutcomeReceiver)} API that executes the whole operation in one call. * * @param request the request specifying type(s) of credentials to get from the user * @param cancellationSignal an optional signal that allows for cancelling this call * @param executor the callback will take place on this {@link Executor} * @param callback the callback invoked when the request succeeds or fails */ public void prepareGetCredential( @NonNull GetCredentialRequest request, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver< PrepareGetCredentialResponse, GetCredentialException> callback) { requireNonNull(request, "request must not be null"); requireNonNull(executor, "executor must not be null"); requireNonNull(callback, "callback must not be null"); if (cancellationSignal != null && cancellationSignal.isCanceled()) { Log.w(TAG, "prepareGetCredential already canceled"); return; } ICancellationSignal cancelRemote = null; GetCredentialTransportPendingUseCase getCredentialTransport = new GetCredentialTransportPendingUseCase(); try { cancelRemote = mService.executePrepareGetCredential( request, new PrepareGetCredentialTransport( executor, callback, getCredentialTransport), getCredentialTransport, mContext.getOpPackageName()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } if (cancellationSignal != null && cancelRemote != null) { cancellationSignal.setRemote(cancelRemote); } } /** * Launches the necessary flows to register an app credential for the user. * *

The execution can potentially launch UI flows to collect user consent to creating or * storing the new credential, etc. * Callers (e.g. browsers) may optionally set origin in {@link CreateCredentialRequest} for an * app different from their own, to be able to get credentials on behalf of that app. They would * need additional permission {@code CREDENTIAL_MANAGER_SET_ORIGIN} * to use this functionality * * @param context the context used to launch any UI needed; use an activity context to make sure * the UI will be launched within the same task stack * @param request the request specifying type(s) of credentials to get from the user * @param cancellationSignal an optional signal that allows for cancelling this call * @param executor the callback will take place on this {@link Executor} * @param callback the callback invoked when the request succeeds or fails */ public void createCredential( @NonNull Context context, @NonNull CreateCredentialRequest request, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver callback) { requireNonNull(request, "request must not be null"); requireNonNull(context, "context must not be null"); requireNonNull(executor, "executor must not be null"); requireNonNull(callback, "callback must not be null"); if (cancellationSignal != null && cancellationSignal.isCanceled()) { Log.w(TAG, "createCredential already canceled"); return; } ICancellationSignal cancelRemote = null; try { cancelRemote = mService.executeCreateCredential( request, new CreateCredentialTransport(context, executor, callback), mContext.getOpPackageName()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } if (cancellationSignal != null && cancelRemote != null) { cancellationSignal.setRemote(cancelRemote); } } /** * Clears the current user credential state from all credential providers. * *

You should invoked this api after your user signs out of your app to notify all credential * providers that any stored credential session for the given app should be cleared. * *

A credential provider may have stored an active credential session and use it to limit * sign-in options for future get-credential calls. For example, it may prioritize the active * credential over any other available credential. When your user explicitly signs out of your * app and in order to get the holistic sign-in options the next time, you should call this API * to let the provider clear any stored credential session. * * @param request the request data * @param cancellationSignal an optional signal that allows for cancelling this call * @param executor the callback will take place on this {@link Executor} * @param callback the callback invoked when the request succeeds or fails */ public void clearCredentialState( @NonNull ClearCredentialStateRequest request, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver callback) { requireNonNull(request, "request must not be null"); requireNonNull(executor, "executor must not be null"); requireNonNull(callback, "callback must not be null"); if (cancellationSignal != null && cancellationSignal.isCanceled()) { Log.w(TAG, "clearCredentialState already canceled"); return; } ICancellationSignal cancelRemote = null; try { cancelRemote = mService.clearCredentialState( request, new ClearCredentialStateTransport(executor, callback), mContext.getOpPackageName()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } if (cancellationSignal != null && cancelRemote != null) { cancellationSignal.setRemote(cancelRemote); } } /** * Sets a list of all user configurable credential providers registered on the system. This API * is intended for settings apps. * * @param primaryProviders the primary providers that user selected for saving credentials. In * the most case, there should be only one primary provider, However, * if there are more than one CredentialProviderService in the same APK, * they should be passed in altogether. * @param providers the list of enabled providers. * @param userId the user ID to configure credential manager for * @param executor the callback will take place on this {@link Executor} * @param callback the callback invoked when the request succeeds or fails * @hide */ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setEnabledProviders( @NonNull List primaryProviders, @NonNull List providers, int userId, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver callback) { requireNonNull(executor, "executor must not be null"); requireNonNull(callback, "callback must not be null"); requireNonNull(providers, "providers must not be null"); requireNonNull(primaryProviders, "primaryProviders must not be null"); try { mService.setEnabledProviders( primaryProviders, providers, userId, new SetEnabledProvidersTransport(executor, callback)); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** * Returns {@code true} if the calling application provides a CredentialProviderService that is * enabled for the current user, or {@code false} otherwise. CredentialProviderServices are * enabled on a per-service basis so the individual component name of the service should be * passed in here. Usage of this API is discouraged as it is not fully functional, and * may throw a NullPointerException on certain devices and/or API versions. * * @throws IllegalArgumentException if the componentName package does not match the calling * package name this call will throw an exception * * @throws NullPointerException Usage of this API is discouraged as it is not fully * functional, and may throw a NullPointerException on certain devices and/or API versions * * @param componentName the component name to check is enabled */ public boolean isEnabledCredentialProviderService(@NonNull ComponentName componentName) { requireNonNull(componentName, "componentName must not be null"); try { return mService.isEnabledCredentialProviderService( componentName, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns the list of CredentialProviderInfo for all discovered credential providers on this * device but will include test system providers as well. * * @hide */ @NonNull @TestApi @RequiresPermission( anyOf = { android.Manifest.permission.QUERY_ALL_PACKAGES, android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS }) public List getCredentialProviderServicesForTesting( @ProviderFilter int providerFilter) { try { return mService.getCredentialProviderServicesForTesting(providerFilter); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns the list of CredentialProviderInfo for all discovered credential providers on this * device. * * @hide */ @NonNull @RequiresPermission( anyOf = { android.Manifest.permission.QUERY_ALL_PACKAGES, android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS }) public List getCredentialProviderServices( int userId, @ProviderFilter int providerFilter) { try { return mService.getCredentialProviderServices(userId, providerFilter); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns whether the service is enabled. * * @hide */ @TestApi public static boolean isServiceEnabled(@NonNull Context context) { requireNonNull(context, "context must not be null"); if (context == null) { return false; } CredentialManager credentialManager = (CredentialManager) context.getSystemService(Context.CREDENTIAL_SERVICE); if (credentialManager != null) { return credentialManager.isServiceEnabled(); } return false; } /** * Returns whether the service is enabled. * * @hide */ private boolean isServiceEnabled() { try { return mService.isServiceEnabled(); } catch (RemoteException e) { return false; } } /** * Returns whether the credential description api is enabled. * * @hide */ public static boolean isCredentialDescriptionApiEnabled(Context context) { if (context == null) { return false; } CredentialManager credentialManager = (CredentialManager) context.getSystemService(Context.CREDENTIAL_SERVICE); if (credentialManager != null) { return credentialManager.isCredentialDescriptionApiEnabled(); } return false; } private boolean isCredentialDescriptionApiEnabled() { return DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false); } /** * Registers a {@link CredentialDescription} for an actively provisioned {@link Credential} a * CredentialProvider has. This registry will then be used to determine where to fetch the * requested {@link Credential} from. Not all credential types will be supported. The * distinction will be made by the JetPack layer. For the types that are supported, JetPack will * add a new key-value pair into {@link GetCredentialRequest}. These will not be persistent on * the device. The Credential Providers will need to call this API again upon device reboot. * * @param request the request data * @throws {@link UnsupportedOperationException} if the feature has not been enabled. * @throws {@link com.android.server.credentials.NonCredentialProviderCallerException} if the * calling package name is not also listed as a Credential Provider. * @throws {@link IllegalArgumentException} if the calling Credential Provider can not handle * one or more of the Credential Types that are sent for registration. */ public void registerCredentialDescription( @NonNull RegisterCredentialDescriptionRequest request) { requireNonNull(request, "request must not be null"); try { mService.registerCredentialDescription(request, mContext.getOpPackageName()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** * Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential} * that has been registered previously. * * @param request the request data * @throws {@link UnsupportedOperationException} if the feature has not been enabled. */ public void unregisterCredentialDescription( @NonNull UnregisterCredentialDescriptionRequest request) { requireNonNull(request, "request must not be null"); try { mService.unregisterCredentialDescription(request, mContext.getOpPackageName()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } private static class PrepareGetCredentialTransport extends IPrepareGetCredentialCallback.Stub { // TODO: listen for cancellation to release callback. private final Executor mExecutor; private final OutcomeReceiver< PrepareGetCredentialResponse, GetCredentialException> mCallback; private final GetCredentialTransportPendingUseCase mGetCredentialTransport; private PrepareGetCredentialTransport( Executor executor, OutcomeReceiver callback, GetCredentialTransportPendingUseCase getCredentialTransport) { mExecutor = executor; mCallback = callback; mGetCredentialTransport = getCredentialTransport; } @Override public void onResponse(PrepareGetCredentialResponseInternal response) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> mCallback.onResult( new PrepareGetCredentialResponse(response, mGetCredentialTransport))); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onError(String errorType, String message) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute( () -> mCallback.onError(new GetCredentialException(errorType, message))); } finally { Binder.restoreCallingIdentity(identity); } } } /** @hide */ protected static class GetCredentialTransportPendingUseCase extends IGetCredentialCallback.Stub { @Nullable private PrepareGetCredentialResponse.GetPendingCredentialInternalCallback mCallback = null; private GetCredentialTransportPendingUseCase() {} public void setCallback( PrepareGetCredentialResponse.GetPendingCredentialInternalCallback callback) { if (mCallback == null) { mCallback = callback; } else { throw new IllegalStateException("callback has already been set once"); } } @Override public void onPendingIntent(PendingIntent pendingIntent) { if (mCallback != null) { mCallback.onPendingIntent(pendingIntent); } else { Log.d(TAG, "Unexpected onPendingIntent call before the show invocation"); } } @Override public void onResponse(GetCredentialResponse response) { if (mCallback != null) { final long identity = Binder.clearCallingIdentity(); try { mCallback.onResponse(response); } finally { Binder.restoreCallingIdentity(identity); } } else { Log.d(TAG, "Unexpected onResponse call before the show invocation"); } } @Override public void onError(String errorType, String message) { if (mCallback != null) { final long identity = Binder.clearCallingIdentity(); try { mCallback.onError(errorType, message); } finally { Binder.restoreCallingIdentity(identity); } } else { Log.d(TAG, "Unexpected onError call before the show invocation"); } } } private static class GetCandidateCredentialsTransport extends IGetCandidateCredentialsCallback.Stub { private final Executor mExecutor; private final OutcomeReceiver mCallback; private GetCandidateCredentialsTransport( Executor executor, OutcomeReceiver callback) { mExecutor = executor; mCallback = callback; } @Override public void onResponse(GetCandidateCredentialsResponse response) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> mCallback.onResult(response)); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onError(String errorType, String message) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute( () -> mCallback.onError(new GetCandidateCredentialsException( errorType, message))); } finally { Binder.restoreCallingIdentity(identity); } } } private static class GetCredentialTransport extends IGetCredentialCallback.Stub { // TODO: listen for cancellation to release callback. private final Context mContext; private final Executor mExecutor; private final OutcomeReceiver mCallback; private GetCredentialTransport( Context context, Executor executor, OutcomeReceiver callback) { mContext = context; mExecutor = executor; mCallback = callback; } @Override public void onPendingIntent(PendingIntent pendingIntent) { try { mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0, OPTIONS_SENDER_BAL_OPTIN); } catch (IntentSender.SendIntentException e) { Log.e( TAG, "startIntentSender() failed for intent:" + pendingIntent.getIntentSender(), e); final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> mCallback.onError( new GetCredentialException(GetCredentialException.TYPE_UNKNOWN))); } finally { Binder.restoreCallingIdentity(identity); } } } @Override public void onResponse(GetCredentialResponse response) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> mCallback.onResult(response)); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onError(String errorType, String message) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute( () -> mCallback.onError(new GetCredentialException(errorType, message))); } finally { Binder.restoreCallingIdentity(identity); } } } private static class CreateCredentialTransport extends ICreateCredentialCallback.Stub { // TODO: listen for cancellation to release callback. private final Context mContext; private final Executor mExecutor; private final OutcomeReceiver mCallback; private CreateCredentialTransport( Context context, Executor executor, OutcomeReceiver callback) { mContext = context; mExecutor = executor; mCallback = callback; } @Override public void onPendingIntent(PendingIntent pendingIntent) { try { mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0, OPTIONS_SENDER_BAL_OPTIN); } catch (IntentSender.SendIntentException e) { Log.e( TAG, "startIntentSender() failed for intent:" + pendingIntent.getIntentSender(), e); final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> mCallback.onError( new CreateCredentialException(CreateCredentialException.TYPE_UNKNOWN))); } finally { Binder.restoreCallingIdentity(identity); } } } @Override public void onResponse(CreateCredentialResponse response) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> mCallback.onResult(response)); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onError(String errorType, String message) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute( () -> mCallback.onError(new CreateCredentialException(errorType, message))); } finally { Binder.restoreCallingIdentity(identity); } } } private static class ClearCredentialStateTransport extends IClearCredentialStateCallback.Stub { // TODO: listen for cancellation to release callback. private final Executor mExecutor; private final OutcomeReceiver mCallback; private ClearCredentialStateTransport( Executor executor, OutcomeReceiver callback) { mExecutor = executor; mCallback = callback; } @Override public void onSuccess() { final long identity = Binder.clearCallingIdentity(); try { mCallback.onResult(null); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onError(String errorType, String message) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute( () -> mCallback.onError( new ClearCredentialStateException(errorType, message))); } finally { Binder.restoreCallingIdentity(identity); } } } private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub { // TODO: listen for cancellation to release callback. private final Executor mExecutor; private final OutcomeReceiver mCallback; private SetEnabledProvidersTransport( Executor executor, OutcomeReceiver callback) { mExecutor = executor; mCallback = callback; } public void onResponse(Void result) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> mCallback.onResult(result)); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onResponse() { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> mCallback.onResult(null)); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onError(String errorType, String message) { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute( () -> mCallback.onError( new SetEnabledProvidersException(errorType, message))); } finally { Binder.restoreCallingIdentity(identity); } } } }