Merge "Add basic piping for providers and ux Test: Built & deployed locally Bug: 253155340"

This commit is contained in:
Reema Bajwa
2022-10-28 19:34:15 +00:00
committed by Android (Google) Code Review
17 changed files with 1099 additions and 102 deletions

View File

@@ -85,7 +85,7 @@ public final class CredentialManager {
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.executeGetCredential(request,
new GetCredentialTransport(executor, callback));
new GetCredentialTransport(executor, callback), mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -124,7 +124,8 @@ public final class CredentialManager {
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.executeCreateCredential(request,
new CreateCredentialTransport(executor, callback));
new CreateCredentialTransport(executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}

View File

@@ -29,7 +29,7 @@ import android.os.ICancellationSignal;
*/
interface ICredentialManager {
@nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback);
@nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
}

View File

@@ -43,10 +43,10 @@ public class ProviderData implements Parcelable {
"android.credentials.ui.extra.PROVIDER_DATA_LIST";
@NonNull
private final String mProviderId;
private final String mProviderFlattenedComponentName;
@NonNull
private final String mProviderDisplayName;
@NonNull
@Nullable
private final Icon mIcon;
@NonNull
private final List<Entry> mCredentialEntries;
@@ -58,11 +58,11 @@ public class ProviderData implements Parcelable {
private final @CurrentTimeMillisLong long mLastUsedTimeMillis;
public ProviderData(
@NonNull String providerId, @NonNull String providerDisplayName,
@NonNull Icon icon, @NonNull List<Entry> credentialEntries,
@NonNull String providerFlattenedComponentName, @NonNull String providerDisplayName,
@Nullable Icon icon, @NonNull List<Entry> credentialEntries,
@NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry,
@CurrentTimeMillisLong long lastUsedTimeMillis) {
mProviderId = providerId;
mProviderFlattenedComponentName = providerFlattenedComponentName;
mProviderDisplayName = providerDisplayName;
mIcon = icon;
mCredentialEntries = credentialEntries;
@@ -73,8 +73,8 @@ public class ProviderData implements Parcelable {
/** Returns the unique provider id. */
@NonNull
public String getProviderId() {
return mProviderId;
public String getProviderFlattenedComponentName() {
return mProviderFlattenedComponentName;
}
@NonNull
@@ -82,7 +82,7 @@ public class ProviderData implements Parcelable {
return mProviderDisplayName;
}
@NonNull
@Nullable
public Icon getIcon() {
return mIcon;
}
@@ -108,9 +108,9 @@ public class ProviderData implements Parcelable {
}
protected ProviderData(@NonNull Parcel in) {
String providerId = in.readString8();
mProviderId = providerId;
AnnotationValidations.validate(NonNull.class, null, mProviderId);
String providerFlattenedComponentName = in.readString8();
mProviderFlattenedComponentName = providerFlattenedComponentName;
AnnotationValidations.validate(NonNull.class, null, mProviderFlattenedComponentName);
String providerDisplayName = in.readString8();
mProviderDisplayName = providerDisplayName;
@@ -118,7 +118,6 @@ public class ProviderData implements Parcelable {
Icon icon = in.readTypedObject(Icon.CREATOR);
mIcon = icon;
AnnotationValidations.validate(NonNull.class, null, mIcon);
List<Entry> credentialEntries = new ArrayList<>();
in.readTypedList(credentialEntries, Entry.CREATOR);
@@ -139,7 +138,7 @@ public class ProviderData implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mProviderId);
dest.writeString8(mProviderFlattenedComponentName);
dest.writeString8(mProviderDisplayName);
dest.writeTypedObject(mIcon, flags);
dest.writeTypedList(mCredentialEntries);
@@ -171,26 +170,27 @@ public class ProviderData implements Parcelable {
* @hide
*/
public static class Builder {
private @NonNull String mProviderId;
private @NonNull String mProviderFlattenedComponentName;
private @NonNull String mProviderDisplayName;
private @NonNull Icon mIcon;
private @Nullable Icon mIcon;
private @NonNull List<Entry> mCredentialEntries = new ArrayList<>();
private @NonNull List<Entry> mActionChips = new ArrayList<>();
private @Nullable Entry mAuthenticationEntry = null;
private @CurrentTimeMillisLong long mLastUsedTimeMillis = 0L;
/** Constructor with required properties. */
public Builder(@NonNull String providerId, @NonNull String providerDisplayName,
@NonNull Icon icon) {
mProviderId = providerId;
public Builder(@NonNull String providerFlattenedComponentName,
@NonNull String providerDisplayName,
@Nullable Icon icon) {
mProviderFlattenedComponentName = providerFlattenedComponentName;
mProviderDisplayName = providerDisplayName;
mIcon = icon;
}
/** Sets the unique provider id. */
@NonNull
public Builder setProviderId(@NonNull String providerId) {
mProviderId = providerId;
public Builder setProviderFlattenedComponentName(@NonNull String providerFlattenedComponentName) {
mProviderFlattenedComponentName = providerFlattenedComponentName;
return this;
}
@@ -239,7 +239,8 @@ public class ProviderData implements Parcelable {
/** Builds a {@link ProviderData}. */
@NonNull
public ProviderData build() {
return new ProviderData(mProviderId, mProviderDisplayName, mIcon, mCredentialEntries,
return new ProviderData(mProviderFlattenedComponentName, mProviderDisplayName,
mIcon, mCredentialEntries,
mActionChips, mAuthenticationEntry, mLastUsedTimeMillis);
}
}

View File

@@ -140,8 +140,8 @@ public final class CredentialEntry implements Parcelable {
public static final class Builder {
private String mType;
private Slice mSlice;
private PendingIntent mPendingIntent;
private Credential mCredential;
private PendingIntent mPendingIntent = null;
private Credential mCredential = null;
private boolean mAutoSelectAllowed = false;
/**

View File

@@ -30,6 +30,22 @@ import java.lang.annotation.RetentionPolicy;
public class CredentialProviderException extends Exception {
public static final int ERROR_UNKNOWN = 0;
/**
* For internal use only.
* Error code to be used when the provider request times out.
*
* @hide
*/
public static final int ERROR_TIMEOUT = 1;
/**
* For internal use only.
* Error code to be used when the async task is canceled internally.
*
* @hide
*/
public static final int ERROR_TASK_CANCELED = 2;
private final int mErrorCode;
/**
@@ -37,6 +53,8 @@ public class CredentialProviderException extends Exception {
*/
@IntDef(prefix = {"ERROR_"}, value = {
ERROR_UNKNOWN,
ERROR_TIMEOUT,
ERROR_TASK_CANCELED
})
@Retention(RetentionPolicy.SOURCE)
public @interface CredentialProviderError { }

View File

@@ -18,16 +18,21 @@ package android.service.credentials;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
@@ -48,19 +53,21 @@ public final class CredentialProviderInfo {
@NonNull
private final List<String> mCapabilities;
// TODO: Move the two strings below to CredentialProviderService when ready.
private static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
private static final String SERVICE_INTERFACE =
"android.service.credentials.CredentialProviderService";
@NonNull
private final Context mContext;
@Nullable
private final Drawable mIcon;
@Nullable
private final CharSequence mLabel;
/**
* Constructs an information instance of the credential provider.
*
* @param context The context object
* @param serviceComponent The serviceComponent of the provider service
* @param userId The android userId for which the current process is running
* @param context the context object
* @param serviceComponent the serviceComponent of the provider service
* @param userId the android userId for which the current process is running
* @throws PackageManager.NameNotFoundException If provider service is not found
* @throws SecurityException If provider does not require the relevant permission
*/
public CredentialProviderInfo(@NonNull Context context,
@NonNull ComponentName serviceComponent, int userId)
@@ -68,7 +75,13 @@ public final class CredentialProviderInfo {
this(context, getServiceInfoOrThrow(serviceComponent, userId));
}
private CredentialProviderInfo(@NonNull Context context, @NonNull ServiceInfo serviceInfo) {
/**
* Constructs an information instance of the credential provider.
* @param context the context object
* @param serviceInfo the service info for the provider app. This must be retrieved from the
* {@code PackageManager}
*/
public CredentialProviderInfo(@NonNull Context context, @NonNull ServiceInfo serviceInfo) {
if (!Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE.equals(serviceInfo.permission)) {
Log.i(TAG, "Credential Provider Service from : " + serviceInfo.packageName
+ "does not require permission"
@@ -76,32 +89,43 @@ public final class CredentialProviderInfo {
throw new SecurityException("Service does not require the expected permission : "
+ Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE);
}
mContext = context;
mServiceInfo = serviceInfo;
mCapabilities = new ArrayList<>();
populateProviderCapabilities(context);
mIcon = mServiceInfo.loadIcon(mContext.getPackageManager());
mLabel = mServiceInfo.loadSafeLabel(
mContext.getPackageManager(), 0 /* do not ellipsize */,
TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
populateProviderCapabilities(context, serviceInfo);
}
private void populateProviderCapabilities(@NonNull Context context) {
if (mServiceInfo.applicationInfo.metaData == null) {
return;
}
private void populateProviderCapabilities(@NonNull Context context, ServiceInfo serviceInfo) {
final PackageManager pm = context.getPackageManager();
try {
final int resourceId = mServiceInfo.applicationInfo.metaData.getInt(
CAPABILITY_META_DATA_KEY);
String[] capabilities = context.getResources().getStringArray(resourceId);
if (capabilities == null) {
Log.w(TAG, "No capabilities found for provider: " + mServiceInfo.packageName);
Bundle metadata = serviceInfo.metaData;
Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
if (metadata == null || resources == null) {
Log.i(TAG, "populateProviderCapabilities - metadata or resources is null");
return;
}
String[] capabilities = resources.getStringArray(metadata.getInt(
CredentialProviderService.CAPABILITY_META_DATA_KEY));
if (capabilities == null || capabilities.length == 0) {
Slog.i(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
return;
}
for (String capability : capabilities) {
if (capability.isEmpty()) {
Log.w(TAG, "Skipping empty capability");
Slog.i(TAG, "Skipping empty capability");
continue;
}
Slog.i(TAG, "Capabilities found for provider: " + capability);
mCapabilities.add(capability);
}
} catch (Resources.NotFoundException e) {
Log.w(TAG, "Exception while populating provider capabilities: " + e.getMessage());
} catch (PackageManager.NameNotFoundException e) {
Slog.i(TAG, e.getMessage());
}
}
@@ -135,6 +159,18 @@ public final class CredentialProviderInfo {
return mServiceInfo;
}
/** Returns the service icon. */
@Nullable
public Drawable getServiceIcon() {
return mIcon;
}
/** Returns the service label. */
@Nullable
public CharSequence getServiceLabel() {
return mLabel;
}
/** Returns an immutable list of capabilities this provider service can support. */
@NonNull
public List<String> getCapabilities() {
@@ -145,14 +181,15 @@ public final class CredentialProviderInfo {
* Returns the valid credential provider services available for the user with the
* given {@code userId}.
*/
@NonNull
public static List<CredentialProviderInfo> getAvailableServices(@NonNull Context context,
@UserIdInt int userId) {
final List<CredentialProviderInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos =
context.getPackageManager().queryIntentServicesAsUser(
new Intent(SERVICE_INTERFACE),
PackageManager.GET_META_DATA,
new Intent(CredentialProviderService.SERVICE_INTERFACE),
PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA),
userId);
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
@@ -169,8 +206,9 @@ public final class CredentialProviderInfo {
* Returns the valid credential provider services available for the user, that can
* support the given {@code credentialType}.
*/
@NonNull
public static List<CredentialProviderInfo> getAvailableServicesForCapability(
Context context, @UserIdInt int userId, String credentialType) {
@NonNull Context context, @UserIdInt int userId, @NonNull String credentialType) {
List<CredentialProviderInfo> servicesForCapability = new ArrayList<>();
final List<CredentialProviderInfo> services = getAvailableServices(context, userId);

View File

@@ -42,6 +42,9 @@ import java.util.Objects;
*/
public abstract class CredentialProviderService extends Service {
private static final String TAG = "CredProviderService";
public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
private Handler mHandler;
/**
@@ -71,12 +74,13 @@ public abstract class CredentialProviderService extends Service {
private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
@Override
public void onGetCredentials(GetCredentialsRequest request, ICancellationSignal transport,
public ICancellationSignal onGetCredentials(GetCredentialsRequest request,
IGetCredentialsCallback callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(transport);
Objects.requireNonNull(callback);
ICancellationSignal transport = CancellationSignal.createTransport();
mHandler.sendMessage(obtainMessage(
CredentialProviderService::onGetCredentials,
CredentialProviderService.this, request,
@@ -100,15 +104,17 @@ public abstract class CredentialProviderService extends Service {
}
}
));
return transport;
}
@Override
public void onCreateCredential(CreateCredentialRequest request,
ICancellationSignal transport, ICreateCredentialCallback callback) {
public ICancellationSignal onCreateCredential(CreateCredentialRequest request,
ICreateCredentialCallback callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(transport);
Objects.requireNonNull(callback);
ICancellationSignal transport = CancellationSignal.createTransport();
mHandler.sendMessage(obtainMessage(
CredentialProviderService::onCreateCredential,
CredentialProviderService.this, request,
@@ -132,6 +138,7 @@ public abstract class CredentialProviderService extends Service {
}
}
));
return transport;
}
};

View File

@@ -21,13 +21,14 @@ import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.IGetCredentialsCallback;
import android.service.credentials.ICreateCredentialCallback;
import android.os.ICancellationSignal;
/**
* Interface from the system to a credential provider service.
*
* @hide
*/
oneway interface ICredentialProviderService {
void onGetCredentials(in GetCredentialsRequest request, in ICancellationSignal transport, in IGetCredentialsCallback callback);
void onCreateCredential(in CreateCredentialRequest request, in ICancellationSignal transport, in ICreateCredentialCallback callback);
interface ICredentialProviderService {
ICancellationSignal onGetCredentials(in GetCredentialsRequest request, in IGetCredentialsCallback callback);
ICancellationSignal onCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
}

View File

@@ -35,7 +35,7 @@ class GetFlowUtils {
ProviderInfo(
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerId,
name = it.providerFlattenedComponentName,
displayName = it.providerDisplayName,
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context)
@@ -79,7 +79,7 @@ class CreateFlowUtils {
com.android.credentialmanager.createflow.ProviderInfo(
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerId,
name = it.providerFlattenedComponentName,
displayName = it.providerDisplayName,
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
createOptions = toCreationOptionInfoList(it.credentialEntries, context),

View File

@@ -21,20 +21,28 @@ import static android.content.Context.CREDENTIAL_SERVICE;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageManager;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.SecureSettingsServiceNameResolver;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* Entry point service for credential management.
*
@@ -49,52 +57,99 @@ public final class CredentialManagerService extends
public CredentialManagerService(@NonNull Context context) {
super(context,
new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE),
new SecureSettingsServiceNameResolver(context, Settings.Secure.CREDENTIAL_SERVICE,
/*isMultipleMode=*/true),
null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
}
@Override
protected String getServiceSettingsProperty() {
return Settings.Secure.AUTOFILL_SERVICE;
return Settings.Secure.CREDENTIAL_SERVICE;
}
@Override // from AbstractMasterSystemService
protected CredentialManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
boolean disabled) {
return new CredentialManagerServiceImpl(this, mLock, resolvedUserId);
// This method should not be called for CredentialManagerService as it is configured to use
// multiple services.
Slog.w(TAG, "Should not be here - CredentialManagerService is configured to use "
+ "multiple services");
return null;
}
@Override
@Override // from SystemService
public void onStart() {
Log.i(TAG, "onStart");
publishBinderService(CREDENTIAL_SERVICE, new CredentialManagerServiceStub());
}
@Override // from AbstractMasterSystemService
protected List<CredentialManagerServiceImpl> newServiceListLocked(int resolvedUserId,
boolean disabled, String[] serviceNames) {
if (serviceNames == null || serviceNames.length == 0) {
Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty");
return new ArrayList<>();
}
List<CredentialManagerServiceImpl> serviceList = new ArrayList<>(serviceNames.length);
for (int i = 0; i < serviceNames.length; i++) {
Log.i(TAG, "in newServiceListLocked, service: " + serviceNames[i]);
if (TextUtils.isEmpty(serviceNames[i])) {
continue;
}
try {
serviceList.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
serviceNames[i]));
} catch (PackageManager.NameNotFoundException e) {
Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
} catch (SecurityException e) {
Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
}
}
return serviceList;
}
private void runForUser(@NonNull final Consumer<CredentialManagerServiceImpl> c) {
final int userId = UserHandle.getCallingUserId();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
final List<CredentialManagerServiceImpl> services =
getServiceListForUserLocked(userId);
services.forEach(s -> {
c.accept(s);
});
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
final class CredentialManagerServiceStub extends ICredentialManager.Stub {
@Override
public ICancellationSignal executeGetCredential(
GetCredentialRequest request,
IGetCredentialCallback callback) {
// TODO: implement.
Log.i(TAG, "executeGetCredential");
final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
final CredentialManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
Log.i(TAG, "Got service for : " + userId);
service.getCredential();
}
}
IGetCredentialCallback callback,
final String callingPackage) {
Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
// TODO : Implement cancellation
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
// New request session, scoped for this request only.
final GetRequestSession session = new GetRequestSession(getContext(),
UserHandle.getCallingUserId(),
callback);
// Invoke all services of a user
runForUser((service) -> {
service.getCredential(request, session, callingPackage);
});
return cancelTransport;
}
@Override
public ICancellationSignal executeCreateCredential(
CreateCredentialRequest request,
ICreateCredentialCallback callback) {
ICreateCredentialCallback callback,
String callingPackage) {
// TODO: implement.
Log.i(TAG, "executeCreateCredential");
ICancellationSignal cancelTransport = CancellationSignal.createTransport();

View File

@@ -17,47 +17,94 @@
package com.android.server.credentials;
import android.annotation.NonNull;
import android.app.AppGlobals;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.RemoteException;
import android.util.Log;
import android.credentials.GetCredentialRequest;
import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.GetCredentialsRequest;
import android.util.Slog;
import com.android.server.infra.AbstractPerUserSystemService;
/**
* Per-user implementation of {@link CredentialManagerService}
* Per-user, per remote service implementation of {@link CredentialManagerService}
*/
public final class CredentialManagerServiceImpl extends
AbstractPerUserSystemService<CredentialManagerServiceImpl, CredentialManagerService> {
private static final String TAG = "CredManSysServiceImpl";
protected CredentialManagerServiceImpl(
// TODO(b/210531) : Make final when update flow is fixed
private ComponentName mRemoteServiceComponentName;
private CredentialProviderInfo mInfo;
public CredentialManagerServiceImpl(
@NonNull CredentialManagerService master,
@NonNull Object lock, int userId) {
@NonNull Object lock, int userId, String serviceName)
throws PackageManager.NameNotFoundException {
super(master, lock, userId);
Slog.i(TAG, "in CredentialManagerServiceImpl cons");
// TODO : Replace with newServiceInfoLocked after confirming behavior
mRemoteServiceComponentName = ComponentName.unflattenFromString(serviceName);
mInfo = new CredentialProviderInfo(getContext(), mRemoteServiceComponentName, mUserId);
}
@Override // from PerUserSystemService
protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
throws PackageManager.NameNotFoundException {
ServiceInfo si;
try {
si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
PackageManager.GET_META_DATA, mUserId);
} catch (RemoteException e) {
throw new PackageManager.NameNotFoundException(
"Could not get service for " + serviceComponent);
}
return si;
// TODO : Test update flows with multiple providers
Slog.i(TAG , "newServiceInfoLocked with : " + serviceComponent.getPackageName());
mRemoteServiceComponentName = serviceComponent;
mInfo = new CredentialProviderInfo(getContext(), serviceComponent, mUserId);
return mInfo.getServiceInfo();
}
/**
* Unimplemented getCredentials
*/
public void getCredential() {
Log.i(TAG, "getCredential not implemented");
// TODO : Implement logic
public void getCredential(GetCredentialRequest request, GetRequestSession requestSession,
String callingPackage) {
Slog.i(TAG, "in getCredential in CredManServiceImpl");
if (mInfo == null) {
Slog.i(TAG, "in getCredential in CredManServiceImpl, but mInfo is null");
return;
}
// TODO : Determine if remoteService instance can be reused across requests
final RemoteCredentialService remoteService = new RemoteCredentialService(
getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
ProviderGetSession providerSession = new ProviderGetSession(mInfo,
requestSession, mUserId, remoteService);
// Set the provider info to the session when the request is initiated. This happens here
// because there is one serviceImpl per remote provider, and so we can only retrieve
// the provider information in the scope of this instance, whereas the session is for the
// entire request.
requestSession.addProviderSession(providerSession);
GetCredentialsRequest filteredRequest = getRequestWithValidType(request, callingPackage);
if (filteredRequest != null) {
remoteService.onGetCredentials(getRequestWithValidType(request, callingPackage),
providerSession);
}
}
@Nullable
private GetCredentialsRequest getRequestWithValidType(GetCredentialRequest request,
String callingPackage) {
GetCredentialsRequest.Builder builder =
new GetCredentialsRequest.Builder(callingPackage);
request.getGetCredentialOptions().forEach( option -> {
if (mInfo.hasCapability(option.getType())) {
Slog.i(TAG, "Provider can handle: " + option.getType());
builder.addGetCredentialOption(option);
} else {
Slog.i(TAG, "Skipping request as provider cannot handle it");
}
});
try {
return builder.build();
} catch (IllegalArgumentException | NullPointerException e) {
Slog.i(TAG, "issue with request build: " + e.getMessage());
}
return null;
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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 com.android.server.credentials;
import android.annotation.NonNull;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.credentials.ui.IntentFactory;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
/** Initiates the Credential Manager UI and receives results. */
public class CredentialManagerUi {
private static final String TAG = "CredentialManagerUi";
@NonNull
private final CredentialManagerUiCallback mCallbacks;
@NonNull private final Context mContext;
private final int mUserId;
@NonNull private final ResultReceiver mResultReceiver = new ResultReceiver(
new Handler(Looper.getMainLooper())) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
handleUiResult(resultCode, resultData);
}
};
private void handleUiResult(int resultCode, Bundle resultData) {
if (resultCode == Activity.RESULT_OK) {
UserSelectionDialogResult selection = UserSelectionDialogResult
.fromResultData(resultData);
if (selection != null) {
mCallbacks.onUiSelection(selection);
} else {
Slog.i(TAG, "No selection found in UI result");
}
} else if (resultCode == Activity.RESULT_CANCELED) {
mCallbacks.onUiCancelation();
}
}
/**
* Interface to be implemented by any class that wishes to get callbacks from the UI.
*/
public interface CredentialManagerUiCallback {
/** Called when the user makes a selection. */
void onUiSelection(UserSelectionDialogResult selection);
/** Called when the user cancels the UI. */
void onUiCancelation();
}
public CredentialManagerUi(Context context, int userId,
CredentialManagerUiCallback callbacks) {
Log.i(TAG, "In CredentialManagerUi constructor");
mContext = context;
mUserId = userId;
mCallbacks = callbacks;
}
/**
* Surfaces the Credential Manager bottom sheet UI.
* @param providerDataList the list of provider data from remote providers
*/
public void show(RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
Log.i(TAG, "In show");
Intent intent = IntentFactory.newIntent(requestInfo, providerDataList,
mResultReceiver);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
}

View File

@@ -0,0 +1,164 @@
/*
* 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 com.android.server.credentials;
import android.content.ComponentName;
import android.content.Context;
import android.credentials.Credential;
import android.credentials.GetCredentialResponse;
import android.credentials.IGetCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.RemoteException;
import android.service.credentials.CredentialEntry;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Central session for a single getCredentials request. This class listens to the
* responses from providers, and the UX app, and updates the provider(S) state.
*/
public final class GetRequestSession extends RequestSession {
private static final String TAG = "GetRequestSession";
private final IGetCredentialCallback mClientCallback;
private final Map<String, ProviderGetSession> mProviders;
public GetRequestSession(Context context, int userId,
IGetCredentialCallback callback) {
super(context, userId, RequestInfo.TYPE_GET);
mClientCallback = callback;
mProviders = new HashMap<>();
}
/**
* Adds a new provider to the list of providers that are contributing to this session.
*/
public void addProviderSession(ProviderGetSession providerSession) {
mProviders.put(providerSession.getComponentName().flattenToString(),
providerSession);
}
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName) {
Log.i(TAG, "in onStatusChanged");
if (ProviderSession.isTerminatingStatus(status)) {
Log.i(TAG, "in onStatusChanged terminating status");
ProviderGetSession session = mProviders.remove(componentName.flattenToString());
if (session != null) {
Slog.i(TAG, "Provider session removed.");
} else {
Slog.i(TAG, "Provider session null, did not exist.");
}
} else if (ProviderSession.isCompletionStatus(status)) {
Log.i(TAG, "in onStatusChanged isCompletionStatus status");
onProviderResponseComplete();
}
}
@Override
public void onUiSelection(UserSelectionDialogResult selection) {
String providerId = selection.getProviderId();
ProviderGetSession providerSession = mProviders.get(providerId);
if (providerSession != null) {
CredentialEntry credentialEntry = providerSession.getCredentialEntry(
selection.getEntrySubkey());
if (credentialEntry != null && credentialEntry.getCredential() != null) {
respondToClientAndFinish(credentialEntry.getCredential());
}
// TODO : Handle action chips and authentication selection
return;
}
// TODO : finish session and respond to client if provider not found
}
@Override
public void onUiCancelation() {
// User canceled the activity
// TODO : Send error code to client
finishSession();
}
private void onProviderResponseComplete() {
Log.i(TAG, "in onProviderResponseComplete");
if (isResponseCompleteAcrossProviders()) {
Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
getProviderDataAndInitiateUi();
}
}
private void getProviderDataAndInitiateUi() {
ArrayList<ProviderData> providerDataList = new ArrayList<>();
for (ProviderGetSession session : mProviders.values()) {
Log.i(TAG, "preparing data for : " + session.getComponentName());
providerDataList.add(session.prepareUiData());
}
if (!providerDataList.isEmpty()) {
Log.i(TAG, "provider list not empty about to initiate ui");
initiateUi(providerDataList);
}
}
private void initiateUi(ArrayList<ProviderData> providerDataList) {
mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newGetRequestInfo(
mRequestId, null, mIsFirstUiTurn, ""),
providerDataList));
}
/**
* Iterates over all provider sessions and returns true if all have responded.
*/
private boolean isResponseCompleteAcrossProviders() {
AtomicBoolean isRequestComplete = new AtomicBoolean(true);
mProviders.forEach( (packageName, session) -> {
if (session.getStatus() != ProviderSession.Status.COMPLETE) {
isRequestComplete.set(false);
}
});
return isRequestComplete.get();
}
private void respondToClientAndFinish(Credential credential) {
try {
mClientCallback.onResponse(new GetCredentialResponse(credential));
} catch (RemoteException e) {
e.printStackTrace();
}
finishSession();
}
private void finishSession() {
clearProviderSessions();
}
private void clearProviderSessions() {
for (ProviderGetSession session : mProviders.values()) {
// TODO : Evaluate if we should unbind remote services here or wait for them
// to automatically unbind when idle. Re-binding frequently also has a cost.
//session.destroy();
}
mProviders.clear();
}
}

View File

@@ -0,0 +1,197 @@
/*
* 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 com.android.server.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.slice.Slice;
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderData;
import android.service.credentials.Action;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialsDisplayContent;
import android.service.credentials.GetCredentialsResponse;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Central provider session that listens for provider callbacks, and maintains provider state.
* Will likely split this into remote response state and UI state.
*/
public final class ProviderGetSession extends ProviderSession<GetCredentialsResponse>
implements RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
private static final String TAG = "ProviderGetSession";
// Key to be used as an entry key for a credential entry
private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
private GetCredentialsResponse mResponse;
@NonNull
private final Map<String, CredentialEntry> mUiCredentials = new HashMap<>();
@NonNull
private final Map<String, Action> mUiActions = new HashMap<>();
public ProviderGetSession(CredentialProviderInfo info,
ProviderInternalCallback callbacks,
int userId, RemoteCredentialService remoteCredentialService) {
super(info, callbacks, userId, remoteCredentialService);
setStatus(Status.PENDING);
}
/** Updates the response being maintained in state by this provider session. */
@Override
public void updateResponse(GetCredentialsResponse response) {
if (response.getAuthenticationAction() != null) {
// TODO : Implement authentication logic
} else if (response.getCredentialsDisplayContent() != null) {
Log.i(TAG , "updateResponse with credentialEntries");
mResponse = response;
updateStatusAndInvokeCallback(Status.COMPLETE);
}
}
/** Returns the response being maintained in this provider session. */
@Override
@Nullable
public GetCredentialsResponse getResponse() {
return mResponse;
}
/** Returns the credential entry maintained in state by this provider session. */
@Nullable
public CredentialEntry getCredentialEntry(@NonNull String entryId) {
return mUiCredentials.get(entryId);
}
/** Returns the action entry maintained in state by this provider session. */
@Nullable
public Action getAction(@NonNull String entryId) {
return mUiActions.get(entryId);
}
/** Called when the provider response has been updated by an external source. */
@Override
public void onProviderResponseSuccess(@Nullable GetCredentialsResponse response) {
Log.i(TAG, "in onProviderResponseSuccess");
updateResponse(response);
}
/** Called when the provider response resulted in a failure. */
@Override
public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
updateStatusAndInvokeCallback(toStatus(errorCode));
}
/** Called when provider service dies. */
@Override
public void onProviderServiceDied(RemoteCredentialService service) {
if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
} else {
Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
}
}
@Override
protected final ProviderData prepareUiData() throws IllegalArgumentException {
Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isCompletionStatus(getStatus())) {
Log.i(TAG, "In prepareUiData not complete");
throw new IllegalStateException("Status must be in completion mode");
}
GetCredentialsResponse response = getResponse();
if (response == null) {
Log.i(TAG, "In prepareUiData response null");
throw new IllegalStateException("Response must be in completion mode");
}
if (response.getAuthenticationAction() != null) {
Log.i(TAG, "In prepareUiData auth not null");
return prepareUiProviderDataWithAuthentication(response.getAuthenticationAction());
}
if (response.getCredentialsDisplayContent() != null){
Log.i(TAG, "In prepareUiData credentials not null");
return prepareUiProviderDataWithCredentials(response.getCredentialsDisplayContent());
}
return null;
}
/**
* To be called by {@link ProviderGetSession} when the UI is to be invoked.
*/
@Nullable
private ProviderData prepareUiProviderDataWithCredentials(@NonNull
CredentialsDisplayContent content) {
Log.i(TAG, "in prepareUiProviderData");
List<Entry> credentialEntries = new ArrayList<>();
List<Entry> actionChips = new ArrayList<>();
Entry authenticationEntry = null;
// Populate the credential entries
for (CredentialEntry credentialEntry : content.getCredentialEntries()) {
String entryId = UUID.randomUUID().toString();
mUiCredentials.put(entryId, credentialEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
Slice slice = credentialEntry.getSlice();
// TODO : Remove conversion of string to int after change in Entry class
credentialEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice()));
}
// populate the action chip
for (Action action : content.getActions()) {
String entryId = UUID.randomUUID().toString();
mUiActions.put(entryId, action);
// TODO : Remove conversion of string to int after change in Entry class
actionChips.add(new Entry(ACTION_ENTRY_KEY, entryId,
action.getSlice()));
}
// TODO : Set the correct last used time
return new ProviderData.Builder(mComponentName.flattenToString(),
mProviderInfo.getServiceLabel() == null ? "" :
mProviderInfo.getServiceLabel().toString(),
/*icon=*/null)
.setCredentialEntries(credentialEntries)
.setActionChips(actionChips)
.setAuthenticationEntry(authenticationEntry)
.setLastUsedTimeMillis(0)
.build();
}
/**
* To be called by {@link ProviderGetSession} when the UI is to be invoked.
*/
@Nullable
private ProviderData prepareUiProviderDataWithAuthentication(@NonNull
Action authenticationEntry) {
// TODO : Implement authentication flow
return null;
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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 com.android.server.credentials;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.credentials.ui.ProviderData;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderInfo;
/**
* Provider session storing the state of provider response and ui entries.
* @param <T> The request type expected from the remote provider, for a given request session.
*/
public abstract class ProviderSession<T> implements RemoteCredentialService.ProviderCallbacks<T> {
// Key to be used as the entry key for an action entry
protected static final String ACTION_ENTRY_KEY = "action_key";
@NonNull protected final ComponentName mComponentName;
@NonNull protected final CredentialProviderInfo mProviderInfo;
@NonNull protected final RemoteCredentialService mRemoteCredentialService;
@NonNull protected final int mUserId;
@NonNull protected Status mStatus = Status.NOT_STARTED;
@NonNull protected final ProviderInternalCallback mCallbacks;
/**
* Interface to be implemented by any class that wishes to get a callback when a particular
* provider session's status changes. Typically, implemented by the {@link RequestSession}
* class.
*/
public interface ProviderInternalCallback {
/**
* Called when status changes.
*/
void onProviderStatusChanged(Status status, ComponentName componentName);
}
protected ProviderSession(@NonNull CredentialProviderInfo info,
@NonNull ProviderInternalCallback callbacks,
@NonNull int userId,
@NonNull RemoteCredentialService remoteCredentialService) {
mProviderInfo = info;
mCallbacks = callbacks;
mUserId = userId;
mComponentName = info.getServiceInfo().getComponentName();
mRemoteCredentialService = remoteCredentialService;
}
/** Update the response state stored with the provider session. */
protected abstract void updateResponse (T response);
/** Update the response state stored with the provider session. */
protected abstract T getResponse ();
/** Should be overridden to prepare, and stores state for {@link ProviderData} to be
* shown on the UI. */
protected abstract ProviderData prepareUiData();
/** Provider status at various states of the request session. */
enum Status {
NOT_STARTED,
PENDING,
REQUIRES_AUTHENTICATION,
COMPLETE,
SERVICE_DEAD,
CANCELED
}
protected void setStatus(@NonNull Status status) {
mStatus = status;
}
@NonNull
protected Status getStatus() {
return mStatus;
}
@NonNull
protected ComponentName getComponentName() {
return mComponentName;
}
/** Updates the status .*/
protected void updateStatusAndInvokeCallback(@NonNull Status status) {
setStatus(status);
mCallbacks.onProviderStatusChanged(status, mComponentName);
}
@NonNull
public static Status toStatus(
@CredentialProviderException.CredentialProviderError int errorCode) {
// TODO : Add more mappings as more flows are supported
return Status.CANCELED;
}
/**
* Returns true if the given status means that the provider session must be terminated.
*/
public static boolean isTerminatingStatus(Status status) {
return status == Status.CANCELED || status == Status.SERVICE_DEAD;
}
/**
* Returns true if the given status means that the provider is done getting the response,
* and is ready for user interaction.
*/
public static boolean isCompletionStatus(Status status) {
return status == Status.COMPLETE || status == Status.REQUIRES_AUTHENTICATION;
}
}

View File

@@ -0,0 +1,185 @@
/*
* 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 com.android.server.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderException.CredentialProviderError;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
import android.service.credentials.ICredentialProviderService;
import android.service.credentials.IGetCredentialsCallback;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Slog;
import com.android.internal.infra.ServiceConnector;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
/**
* Handles connections with the remote credential provider
*
* @hide
*/
public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialProviderService>{
private static final String TAG = "RemoteCredentialService";
/** Timeout for a single request. */
private static final long TIMEOUT_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
/** Timeout to unbind after the task queue is empty. */
private static final long TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS =
5 * DateUtils.SECOND_IN_MILLIS;
private final ComponentName mComponentName;
/**
* Callbacks to be invoked when the provider remote service responds with a
* success or failure.
* @param <T> the type of response expected from the provider
*/
public interface ProviderCallbacks<T> {
/** Called when a successful response is received from the remote provider. */
void onProviderResponseSuccess(@Nullable T response);
/** Called when a failure response is received from the remote provider. */
void onProviderResponseFailure(int errorCode, @Nullable CharSequence message);
/** Called when the remote provider service dies. */
void onProviderServiceDied(RemoteCredentialService service);
}
public RemoteCredentialService(@NonNull Context context,
@NonNull ComponentName componentName, int userId) {
super(context, new Intent(CredentialProviderService.SERVICE_INTERFACE)
.setComponent(componentName), Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
userId, ICredentialProviderService.Stub::asInterface);
mComponentName = componentName;
}
/** Unbinds automatically after this amount of time. */
@Override
protected long getAutoDisconnectTimeoutMs() {
return TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS;
}
/** Return the componentName of the service to be connected. */
@NonNull public ComponentName getComponentName() {
return mComponentName;
}
/** Destroys this remote service by unbinding the connection. */
public void destroy() {
unbind();
}
/** Main entry point to be called for executing a getCredential call on the remote
* provider service.
* @param request the request to be sent to the provider
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderSession} class that maintains provider state
*/
public void onGetCredentials(@NonNull GetCredentialsRequest request,
ProviderCallbacks<GetCredentialsResponse> callback) {
Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
AtomicReference<CompletableFuture<GetCredentialsResponse>> futureRef =
new AtomicReference<>();
CompletableFuture<GetCredentialsResponse> connectThenExecute = postAsync(service -> {
CompletableFuture<GetCredentialsResponse> getCredentials = new CompletableFuture<>();
ICancellationSignal cancellationSignal =
service.onGetCredentials(request, new IGetCredentialsCallback.Stub() {
@Override
public void onSuccess(GetCredentialsResponse response) {
Log.i(TAG, "In onSuccess in RemoteCredentialService");
getCredentials.complete(response);
}
@Override
public void onFailure(@CredentialProviderError int errorCode,
CharSequence message) {
Log.i(TAG, "In onFailure in RemoteCredentialService");
String errorMsg = message == null ? "" : String.valueOf(message);
getCredentials.completeExceptionally(new CredentialProviderException(
errorCode, errorMsg));
}
});
CompletableFuture<GetCredentialsResponse> future = futureRef.get();
if (future != null && future.isCancelled()) {
dispatchCancellationSignal(cancellationSignal);
} else {
cancellationSink.set(cancellationSignal);
}
return getCredentials;
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> {
if (error == null) {
Log.i(TAG, "In RemoteCredentialService execute error is null");
callback.onProviderResponseSuccess(result);
} else {
if (error instanceof TimeoutException) {
Log.i(TAG, "In RemoteCredentialService execute error is timeout");
dispatchCancellationSignal(cancellationSink.get());
callback.onProviderResponseFailure(
CredentialProviderException.ERROR_TIMEOUT,
error.getMessage());
} else if (error instanceof CancellationException) {
Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
dispatchCancellationSignal(cancellationSink.get());
callback.onProviderResponseFailure(
CredentialProviderException.ERROR_TASK_CANCELED,
error.getMessage());
} else if (error instanceof CredentialProviderException) {
Log.i(TAG, "In RemoteCredentialService execute error is provider error");
callback.onProviderResponseFailure(((CredentialProviderException) error)
.getErrorCode(),
error.getMessage());
} else {
Log.i(TAG, "In RemoteCredentialService execute error is unknown");
callback.onProviderResponseFailure(
CredentialProviderException.ERROR_UNKNOWN,
error.getMessage());
}
}
}));
}
private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
if (signal == null) {
Slog.e(TAG, "Error dispatching a cancellation - Signal is null");
return;
}
try {
signal.cancel();
} catch (RemoteException e) {
Slog.e(TAG, "Error dispatching a cancellation", e);
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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 com.android.server.credentials;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
/**
* Base class of a request session, that listens to UI events. This class must be extended
* every time a new response type is expected from the providers.
*/
abstract class RequestSession implements CredentialManagerUi.CredentialManagerUiCallback,
ProviderSession.ProviderInternalCallback {
@NonNull protected final IBinder mRequestId;
@NonNull protected final Context mContext;
@NonNull protected final CredentialManagerUi mCredentialManagerUi;
@NonNull protected final String mRequestType;
@NonNull protected final Handler mHandler;
@NonNull protected boolean mIsFirstUiTurn = true;
@UserIdInt protected final int mUserId;
protected RequestSession(@NonNull Context context,
@UserIdInt int userId, @NonNull String requestType) {
mContext = context;
mUserId = userId;
mRequestType = requestType;
mHandler = new Handler(Looper.getMainLooper(), null, true);
mRequestId = new Binder();
mCredentialManagerUi = new CredentialManagerUi(mContext,
mUserId, this);
}
/** Returns the unique identifier of this request session. */
public IBinder getRequestId() {
return mRequestId;
}
@Override // from CredentialManagerUiCallback
public abstract void onUiSelection(UserSelectionDialogResult selection);
@Override // from CredentialManagerUiCallback
public abstract void onUiCancelation();
@Override // from ProviderInternalCallback
public abstract void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName);
}