Merge "Add basic piping for providers and ux Test: Built & deployed locally Bug: 253155340"
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user