421 lines
18 KiB
Java
421 lines
18 KiB
Java
![]() |
/*
|
||
|
* 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}
|
||
|
*
|
||
|
* <p>
|
||
|
* 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}
|
||
|
*
|
||
|
* <p>
|
||
|
* 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}.
|
||
|
*
|
||
|
* <p>
|
||
|
* 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}.
|
||
|
*
|
||
|
* <p>
|
||
|
* 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.
|
||
|
*
|
||
|
* <p>
|
||
|
* 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.
|
||
|
*
|
||
|
* <p>The result must be set through {@link android.app.Activity#setResult} as an intent extra
|
||
|
*
|
||
|
* <p>
|
||
|
* 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.
|
||
|
*
|
||
|
* <p>
|
||
|
* 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.
|
||
|
*
|
||
|
* <p>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.
|
||
|
*
|
||
|
* <p>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.
|
||
|
*
|
||
|
* <p>
|
||
|
* 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
|
||
|
* <code><{@link android.R.styleable#CredentialProvider credential-provider}></code>
|
||
|
* tag.
|
||
|
*
|
||
|
* For example (AndroidManifest.xml):
|
||
|
* <code>
|
||
|
* <meta-data
|
||
|
* android:name="android.credentials.provider"
|
||
|
* android:resource="@xml/provider"/>
|
||
|
* </code>
|
||
|
*
|
||
|
* For example (xml/provider.xml):
|
||
|
* <code>
|
||
|
* <credential-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||
|
* android:settingsSubtitle="@string/providerSubtitle">
|
||
|
* <capabilities>
|
||
|
* <capability>@string/passwords</capability>
|
||
|
* <capability>@string/passkeys</capability>
|
||
|
* </capabilities>
|
||
|
* <capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
|
||
|
* <capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
|
||
|
* </credential-provider>
|
||
|
* </code>
|
||
|
*/
|
||
|
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.
|
||
|
*
|
||
|
* <p>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<BeginGetCredentialResponse,
|
||
|
GetCredentialException>() {
|
||
|
@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<Void, ClearCredentialStateException>() {
|
||
|
@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.
|
||
|
*
|
||
|
* <p>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.
|
||
|
*
|
||
|
* <p>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<BeginCreateCredentialResponse,
|
||
|
CreateCredentialException> 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<Void,
|
||
|
ClearCredentialStateException> callback);
|
||
|
}
|