/* * Copyright (C) 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.service.credentials; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.SdkConstant; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.credentials.ClearCredentialStateException; import android.credentials.CreateCredentialException; import android.credentials.GetCredentialException; import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; import android.os.ICancellationSignal; import android.os.Looper; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.util.Slog; import java.util.Objects; /** * Service to be extended by credential providers, in order to return user credentials * to the framework. */ public abstract class CredentialProviderService extends Service { /** * Intent extra: The {@link android.credentials.CreateCredentialRequest} attached with * the {@code pendingIntent} that is invoked when the user selects a {@link CreateEntry} * returned as part of the {@link BeginCreateCredentialResponse} * *

* Type: {@link android.service.credentials.CreateCredentialRequest} */ public static final String EXTRA_CREATE_CREDENTIAL_REQUEST = "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST"; /** * Intent extra: The {@link GetCredentialRequest} attached with * the {@code pendingIntent} that is invoked when the user selects a {@link CredentialEntry} * returned as part of the {@link BeginGetCredentialResponse} * *

* Type: {@link GetCredentialRequest} */ public static final String EXTRA_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.GET_CREDENTIAL_REQUEST"; /** * Intent extra: The result of a create flow operation, to be set on finish of the * {@link android.app.Activity} invoked through the {@code pendingIntent} set on * a {@link CreateEntry}. * *

* Type: {@link android.credentials.CreateCredentialResponse} */ public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE = "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE"; /** * Intent extra: The result of a get credential flow operation, to be set on finish of the * {@link android.app.Activity} invoked through the {@code pendingIntent} set on * a {@link CredentialEntry}. * *

* Type: {@link android.credentials.GetCredentialResponse} */ public static final String EXTRA_GET_CREDENTIAL_RESPONSE = "android.service.credentials.extra.GET_CREDENTIAL_RESPONSE"; /** * Intent extra: The result of an authentication flow, to be set on finish of the * {@link android.app.Activity} invoked through the {@link android.app.PendingIntent} set on * an authentication {@link Action}, as part of the original * {@link BeginGetCredentialResponse}. This result should contain the actual content, * including credential entries and action entries, to be shown on the selector. * *

* Type: {@link BeginGetCredentialResponse} */ public static final String EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_RESPONSE"; /** * Intent extra: The failure exception set at the final stage of a get flow. * This exception is set at the finishing result of the {@link android.app.Activity} * invoked by the {@link PendingIntent} , when a user selects the {@link CredentialEntry} * that contained the {@link PendingIntent} in question. * *

The result must be set through {@link android.app.Activity#setResult} as an intent extra * *

* Type: {@link android.credentials.GetCredentialException} */ public static final String EXTRA_GET_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.GET_CREDENTIAL_EXCEPTION"; /** * Intent extra: The failure exception set at the final stage of a create flow. * This exception is set at the finishing result of the {@link android.app.Activity} * invoked by the {@link PendingIntent} , when a user selects the {@link CreateEntry} * that contained the {@link PendingIntent} in question. * *

* Type: {@link android.credentials.CreateCredentialException} */ public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION"; /** * Intent extra: The {@link BeginGetCredentialRequest} attached with * the {@code pendingIntent} that is invoked when the user selects an * authentication entry (intending to unlock the provider app) on the UI. * *

When a provider app receives a {@link BeginGetCredentialRequest} through the * {@link CredentialProviderService#onBeginGetCredential} call, it can construct the * {@link BeginGetCredentialResponse} with either an authentication {@link Action} (if the app * is locked), or a {@link BeginGetCredentialResponse} (if the app is unlocked). In the former * case, i.e. the app is locked, user will be shown the authentication action. When selected, * the underlying {@link PendingIntent} will be invoked which will lead the user to provider's * unlock activity. This pending intent will also contain the original * {@link BeginGetCredentialRequest} to be retrieved and processed after the unlock * flow is complete. * *

After the app is unlocked, the {@link BeginGetCredentialResponse} must be constructed * using a {@link BeginGetCredentialResponse}, which must be set on an {@link Intent} as an * intent extra against CredentialProviderService#EXTRA_CREDENTIALS_RESPONSE_CONTENT}. * This intent should then be set as a result through {@link android.app.Activity#setResult} * before finishing the activity. * *

* Type: {@link BeginGetCredentialRequest} */ public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST"; /** * The key to autofillId associated with the requested credential option and the corresponding * credential entry. The associated autofillId will be contained inside the candidate query * bundle of {@link android.credentials.CredentialOption} if requested through the * {@link com.android.credentialmanager.autofill.CredentialAutofillService}. The resulting * credential entry will contain the autofillId inside its framework extras intent. * * @hide */ public static final String EXTRA_AUTOFILL_ID = "android.service.credentials.extra.AUTOFILL_ID"; private static final String TAG = "CredProviderService"; /** * Name under which a Credential Provider service component publishes information * about itself. This meta-data must reference an XML resource containing * an * <{@link android.R.styleable#CredentialProvider credential-provider}> * tag. * * For example (AndroidManifest.xml): * * * * * For example (xml/provider.xml): * * * * @string/passwords * @string/passkeys * * * * * */ public static final String SERVICE_META_DATA = "android.credentials.provider"; /** @hide */ public static final String TEST_SYSTEM_PROVIDER_META_DATA_KEY = "android.credentials.testsystemprovider"; private Handler mHandler; /** * The {@link Intent} that must be declared as handled by the service. The service must also * require the {android.Manifest.permission#BIND_CREDENTIAL_PROVIDER_SERVICE} permission * so that only the system can bind to it. */ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.service.credentials.CredentialProviderService"; /** * The {@link Intent} that must be declared as handled by a system credential provider * service. * *

The service must also require the * {android.Manifest.permission#BIND_CREDENTIAL_PROVIDER_SERVICE} permission * so that only the system can bind to it. * * @hide */ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SYSTEM_SERVICE_INTERFACE = "android.service.credentials.system.CredentialProviderService"; @CallSuper @Override public void onCreate() { super.onCreate(); mHandler = new Handler(Looper.getMainLooper(), null, true); } @Override @NonNull public final IBinder onBind(@NonNull Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction())) { return mInterface.asBinder(); } Slog.w(TAG, "Failed to bind with intent: " + intent); return null; } private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() { @Override public void onBeginGetCredential(BeginGetCredentialRequest request, IBeginGetCredentialCallback callback) { Objects.requireNonNull(request); Objects.requireNonNull(callback); ICancellationSignal transport = CancellationSignal.createTransport(); try { callback.onCancellable(transport); } catch (RemoteException e) { e.rethrowFromSystemServer(); } mHandler.sendMessage(obtainMessage( CredentialProviderService::onBeginGetCredential, CredentialProviderService.this, request, CancellationSignal.fromTransport(transport), new OutcomeReceiver() { @Override public void onResult(BeginGetCredentialResponse result) { try { callback.onSuccess(result); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } @Override public void onError(GetCredentialException e) { try { callback.onFailure(e.getType(), e.getMessage()); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } } } )); } @Override public void onBeginCreateCredential(BeginCreateCredentialRequest request, IBeginCreateCredentialCallback callback) { Objects.requireNonNull(request); Objects.requireNonNull(callback); ICancellationSignal transport = CancellationSignal.createTransport(); try { callback.onCancellable(transport); } catch (RemoteException e) { e.rethrowFromSystemServer(); } mHandler.sendMessage(obtainMessage( CredentialProviderService::onBeginCreateCredential, CredentialProviderService.this, request, CancellationSignal.fromTransport(transport), new OutcomeReceiver< BeginCreateCredentialResponse, CreateCredentialException>() { @Override public void onResult(BeginCreateCredentialResponse result) { try { callback.onSuccess(result); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } @Override public void onError(CreateCredentialException e) { try { callback.onFailure(e.getType(), e.getMessage()); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } } } )); } @Override public void onClearCredentialState(ClearCredentialStateRequest request, IClearCredentialStateCallback callback) { Objects.requireNonNull(request); Objects.requireNonNull(callback); ICancellationSignal transport = CancellationSignal.createTransport(); try { callback.onCancellable(transport); } catch (RemoteException e) { e.rethrowFromSystemServer(); } mHandler.sendMessage(obtainMessage( CredentialProviderService::onClearCredentialState, CredentialProviderService.this, request, CancellationSignal.fromTransport(transport), new OutcomeReceiver() { @Override public void onResult(Void result) { try { callback.onSuccess(); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } @Override public void onError(ClearCredentialStateException e) { try { callback.onFailure(e.getType(), e.getMessage()); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } } } )); } }; /** * Called by the android system to retrieve user credentials from the connected provider * service. * *

This API denotes a query stage request for getting user's credentials from a given * credential provider. The request contains a list of * {@link BeginGetCredentialOption} that have parameters to be used for * populating candidate credentials, as a list of {@link CredentialEntry} to be set * on the {@link BeginGetCredentialResponse}. This list is then shown to the user on a * selector. * *

If a {@link PendingIntent} is set on a {@link CredentialEntry}, and the user selects that * entry, a {@link GetCredentialRequest} with all parameters needed to get the actual * {@link android.credentials.Credential} will be sent as part of the {@link Intent} fired * through the {@link PendingIntent}. * @param request the request for the provider to handle * @param cancellationSignal signal for providers to listen to any cancellation requests from * the android system * @param callback object used to relay the response of the credentials request */ public abstract void onBeginGetCredential(@NonNull BeginGetCredentialRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver< BeginGetCredentialResponse, GetCredentialException> callback); /** * Called by the android system to create a credential. * @param request The credential creation request for the provider to handle. * @param cancellationSignal Signal for providers to listen to any cancellation requests from * the android system. * @param callback Object used to relay the response of the credential creation request. */ public abstract void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver callback); /** * Called by the android system to clear the credential state. * * This api isinvoked by developers after users sign out of an app, with an intention to * clear any stored credential session that providers have retained. * * As a provider, you must clear any credential state, if maintained. For e.g. a provider may * have stored an active credential session that is used to limit or rank sign-in options for * future credential retrieval flows. When a user signs out of the app, such state should be * cleared and an exhaustive list of credentials must be presented to the user on subsequent * credential retrieval flows. * * @param request The clear credential request for the provider to handle. * @param cancellationSignal Signal for providers to listen to any cancellation requests from * the android system. * @param callback Object used to relay the result of the request. */ public abstract void onClearCredentialState(@NonNull ClearCredentialStateRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver callback); }