summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/credentials/CredentialManager.java5
-rw-r--r--core/java/android/credentials/ICredentialManager.aidl4
-rw-r--r--core/java/android/credentials/ui/ProviderData.java43
-rw-r--r--core/java/android/service/credentials/CredentialEntry.java4
-rw-r--r--core/java/android/service/credentials/CredentialProviderException.java18
-rw-r--r--core/java/android/service/credentials/CredentialProviderInfo.java88
-rw-r--r--core/java/android/service/credentials/CredentialProviderService.java17
-rw-r--r--core/java/android/service/credentials/ICredentialProviderService.aidl7
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt4
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java91
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java87
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerUi.java92
-rw-r--r--services/credentials/java/com/android/server/credentials/GetRequestSession.java164
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderGetSession.java197
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderSession.java124
-rw-r--r--services/credentials/java/com/android/server/credentials/RemoteCredentialService.java185
-rw-r--r--services/credentials/java/com/android/server/credentials/RequestSession.java67
17 files changed, 1097 insertions, 100 deletions
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index b9cef0fccddf..30ee118d3b59 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -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();
}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index dcf7106d15eb..b0f27f9164f3 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -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);
}
diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java
index 35e12fa43b28..3728469d723d 100644
--- a/core/java/android/credentials/ui/ProviderData.java
+++ b/core/java/android/credentials/ui/ProviderData.java
@@ -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);
}
}
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 4cc43a10e88f..a3fa979a6188 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -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;
/**
diff --git a/core/java/android/service/credentials/CredentialProviderException.java b/core/java/android/service/credentials/CredentialProviderException.java
index b39b4a0cc180..06f0052a29a9 100644
--- a/core/java/android/service/credentials/CredentialProviderException.java
+++ b/core/java/android/service/credentials/CredentialProviderException.java
@@ -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 { }
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java
index e3f8cb7bb23e..2c7a983826f6 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfo.java
@@ -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);
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 1cdf186d898c..b1b08f466622 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -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;
}
};
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index c68430ce752e..c21cefab701a 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -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);
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index e037db7aa0e2..2ba8748c16b7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -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),
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 91f5c6999427..40412db2ed5e 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -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");
+ IGetCredentialCallback callback,
+ final String callingPackage) {
+ Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
+ // TODO : Implement cancellation
+ ICancellationSignal cancelTransport = CancellationSignal.createTransport();
- final int userId = UserHandle.getCallingUserId();
- synchronized (mLock) {
- final CredentialManagerServiceImpl service = peekServiceForUserLocked(userId);
- if (service != null) {
- Log.i(TAG, "Got service for : " + userId);
- service.getCredential();
- }
- }
+ // New request session, scoped for this request only.
+ final GetRequestSession session = new GetRequestSession(getContext(),
+ UserHandle.getCallingUserId(),
+ callback);
- ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ // 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();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index aa19241e77dd..cc03f9b89119 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -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);
+ // 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();
+ }
+
+ 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);
}
- return si;
}
- /**
- * Unimplemented getCredentials
- */
- public void getCredential() {
- Log.i(TAG, "getCredential not implemented");
- // TODO : Implement logic
+ @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;
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
new file mode 100644
index 000000000000..69fb1eaa7543
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -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);
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
new file mode 100644
index 000000000000..80f0fec06825
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -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();
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
new file mode 100644
index 000000000000..24610df7ad79
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -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;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
new file mode 100644
index 000000000000..3a9f96432d8c
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -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;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
new file mode 100644
index 000000000000..d0b6e7d6238c
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -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);
+ }
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
new file mode 100644
index 000000000000..1bacbb342edb
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -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);
+}