summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/credentials/CredentialManager.java138
-rw-r--r--core/java/android/credentials/ICredentialManager.aidl5
-rw-r--r--core/java/android/credentials/ISetEnabledProvidersCallback.aidl27
-rw-r--r--core/java/android/credentials/SetEnabledProvidersException.java74
-rw-r--r--core/java/android/credentials/SetEnabledProvidersRequest.aidl19
-rw-r--r--core/java/android/credentials/SetEnabledProvidersRequest.java81
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java44
7 files changed, 353 insertions, 35 deletions
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index c011949ec6d4..a578956fedd5 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -21,6 +21,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.app.Activity;
import android.app.PendingIntent;
@@ -32,6 +33,7 @@ import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.util.Log;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -40,9 +42,9 @@ import java.util.concurrent.Executor;
* <p>Note that an application should call the Jetpack CredentialManager apis instead of directly
* calling these framework apis.
*
- * <p>The CredentialManager apis launch framework UI flows for a user to
- * register a new credential or to consent to a saved credential from supported credential
- * providers, which can then be used to authenticate to the app.
+ * <p>The CredentialManager apis launch framework UI flows for a user to register a new credential
+ * or to consent to a saved credential from supported credential providers, which can then be used
+ * to authenticate to the app.
*/
@SystemService(Context.CREDENTIAL_SERVICE)
public final class CredentialManager {
@@ -76,8 +78,7 @@ public final class CredentialManager {
@NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
- @NonNull OutcomeReceiver<
- GetCredentialResponse, GetCredentialException> callback) {
+ @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
requireNonNull(request, "request must not be null");
requireNonNull(activity, "activity must not be null");
requireNonNull(executor, "executor must not be null");
@@ -90,10 +91,11 @@ public final class CredentialManager {
ICancellationSignal cancelRemote = null;
try {
- cancelRemote = mService.executeGetCredential(
- request,
- new GetCredentialTransport(activity, executor, callback),
- mContext.getOpPackageName());
+ cancelRemote =
+ mService.executeGetCredential(
+ request,
+ new GetCredentialTransport(activity, executor, callback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -106,8 +108,8 @@ public final class CredentialManager {
/**
* Launches the necessary flows to register an app credential for the user.
*
- * <p>The execution can potentially launch UI flows to collect user consent to creating
- * or storing the new credential, etc.
+ * <p>The execution can potentially launch UI flows to collect user consent to creating or
+ * storing the new credential, etc.
*
* @param request the request specifying type(s) of credentials to get from the user
* @param activity the activity used to launch any UI needed
@@ -120,8 +122,8 @@ public final class CredentialManager {
@NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
- @NonNull OutcomeReceiver<
- CreateCredentialResponse, CreateCredentialException> callback) {
+ @NonNull
+ OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
requireNonNull(request, "request must not be null");
requireNonNull(activity, "activity must not be null");
requireNonNull(executor, "executor must not be null");
@@ -134,9 +136,11 @@ public final class CredentialManager {
ICancellationSignal cancelRemote = null;
try {
- cancelRemote = mService.executeCreateCredential(request,
- new CreateCredentialTransport(activity, executor, callback),
- mContext.getOpPackageName());
+ cancelRemote =
+ mService.executeCreateCredential(
+ request,
+ new CreateCredentialTransport(activity, executor, callback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -149,10 +153,10 @@ public final class CredentialManager {
/**
* Clears the current user credential state from all credential providers.
*
- * You should invoked this api after your user signs out of your app to notify all credential
+ * <p>You should invoked this api after your user signs out of your app to notify all credential
* providers that any stored credential session for the given app should be cleared.
*
- * A credential provider may have stored an active credential session and use it to limit
+ * <p>A credential provider may have stored an active credential session and use it to limit
* sign-in options for future get-credential calls. For example, it may prioritize the active
* credential over any other available credential. When your user explicitly signs out of your
* app and in order to get the holistic sign-in options the next time, you should call this API
@@ -178,9 +182,11 @@ public final class CredentialManager {
ICancellationSignal cancelRemote = null;
try {
- cancelRemote = mService.clearCredentialState(request,
- new ClearCredentialStateTransport(executor, callback),
- mContext.getOpPackageName());
+ cancelRemote =
+ mService.clearCredentialState(
+ request,
+ new ClearCredentialStateTransport(executor, callback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -190,15 +196,44 @@ public final class CredentialManager {
}
}
+ /**
+ * Sets a list of all user configurable credential providers registered on the system. This API
+ * is intended for settings apps.
+ *
+ * @param providers the list of enabled providers
+ * @param userId the user ID to configure credential manager for
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void setEnabledProviders(
+ @NonNull List<String> providers,
+ int userId,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, SetEnabledProvidersException> callback) {
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+ requireNonNull(providers, "providers must not be null");
+
+ try {
+ mService.setEnabledProviders(
+ providers, userId, new SetEnabledProvidersTransport(executor, callback));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
private final Activity mActivity;
private final Executor mExecutor;
- private final OutcomeReceiver<
- GetCredentialResponse, GetCredentialException> mCallback;
+ private final OutcomeReceiver<GetCredentialResponse, GetCredentialException> mCallback;
- private GetCredentialTransport(Activity activity, Executor executor,
+ private GetCredentialTransport(
+ Activity activity,
+ Executor executor,
OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
mActivity = activity;
mExecutor = executor;
@@ -210,8 +245,10 @@ public final class CredentialManager {
try {
mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
- Log.e(TAG, "startIntentSender() failed for intent:"
- + pendingIntent.getIntentSender(), e);
+ Log.e(
+ TAG,
+ "startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
+ e);
// TODO: propagate the error.
}
}
@@ -233,10 +270,12 @@ public final class CredentialManager {
private final Activity mActivity;
private final Executor mExecutor;
- private final OutcomeReceiver<
- CreateCredentialResponse, CreateCredentialException> mCallback;
+ private final OutcomeReceiver<CreateCredentialResponse, CreateCredentialException>
+ mCallback;
- private CreateCredentialTransport(Activity activity, Executor executor,
+ private CreateCredentialTransport(
+ Activity activity,
+ Executor executor,
OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
mActivity = activity;
mExecutor = executor;
@@ -248,8 +287,10 @@ public final class CredentialManager {
try {
mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
- Log.e(TAG, "startIntentSender() failed for intent:"
- + pendingIntent.getIntentSender(), e);
+ Log.e(
+ TAG,
+ "startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
+ e);
// TODO: propagate the error.
}
}
@@ -266,15 +307,14 @@ public final class CredentialManager {
}
}
- private static class ClearCredentialStateTransport
- extends IClearCredentialStateCallback.Stub {
+ private static class ClearCredentialStateTransport extends IClearCredentialStateCallback.Stub {
// TODO: listen for cancellation to release callback.
private final Executor mExecutor;
private final OutcomeReceiver<Void, ClearCredentialStateException> mCallback;
- private ClearCredentialStateTransport(Executor executor,
- OutcomeReceiver<Void, ClearCredentialStateException> callback) {
+ private ClearCredentialStateTransport(
+ Executor executor, OutcomeReceiver<Void, ClearCredentialStateException> callback) {
mExecutor = executor;
mCallback = callback;
}
@@ -290,4 +330,32 @@ public final class CredentialManager {
() -> mCallback.onError(new ClearCredentialStateException(errorType, message)));
}
}
+
+ private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub {
+ // TODO: listen for cancellation to release callback.
+
+ private final Executor mExecutor;
+ private final OutcomeReceiver<Void, SetEnabledProvidersException> mCallback;
+
+ private SetEnabledProvidersTransport(
+ Executor executor, OutcomeReceiver<Void, SetEnabledProvidersException> callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ public void onResponse(Void result) {
+ mExecutor.execute(() -> mCallback.onResult(result));
+ }
+
+ @Override
+ public void onResponse() {
+ mExecutor.execute(() -> mCallback.onResult(null));
+ }
+
+ @Override
+ public void onError(String errorType, String message) {
+ mExecutor.execute(
+ () -> mCallback.onError(new SetEnabledProvidersException(errorType, message)));
+ }
+ }
}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index c5497bdbdc0f..d8c4d894a439 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -16,12 +16,15 @@
package android.credentials;
+import java.util.List;
+
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
+import android.credentials.ISetEnabledProvidersCallback;
import android.os.ICancellationSignal;
/**
@@ -36,4 +39,6 @@ interface ICredentialManager {
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
+
+ void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
}
diff --git a/core/java/android/credentials/ISetEnabledProvidersCallback.aidl b/core/java/android/credentials/ISetEnabledProvidersCallback.aidl
new file mode 100644
index 000000000000..30442786d11f
--- /dev/null
+++ b/core/java/android/credentials/ISetEnabledProvidersCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+/**
+ * Listener for an setEnabledProviders request.
+ *
+ * @hide
+ */
+interface ISetEnabledProvidersCallback {
+ oneway void onResponse();
+ oneway void onError(String errorType, String message);
+} \ No newline at end of file
diff --git a/core/java/android/credentials/SetEnabledProvidersException.java b/core/java/android/credentials/SetEnabledProvidersException.java
new file mode 100644
index 000000000000..6178f349e736
--- /dev/null
+++ b/core/java/android/credentials/SetEnabledProvidersException.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Represents an error encountered during the {@link
+ * CredentialManager#setEnabledProviders(CancellationSignal Executor, OutcomeReceiver)} operation.
+ *
+ * @hide
+ */
+public class SetEnabledProvidersException extends Exception {
+
+ @NonNull public final String errorType;
+
+ /**
+ * Constructs a {@link SetEnabledProvidersException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public SetEnabledProvidersException(@NonNull String errorType, @Nullable String message) {
+ this(errorType, message, null);
+ }
+
+ /**
+ * Constructs a {@link SetEnabledProvidersException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public SetEnabledProvidersException(
+ @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+ super(message, cause);
+ this.errorType =
+ Preconditions.checkStringNotEmpty(errorType, "errorType must not be empty");
+ }
+
+ /**
+ * Constructs a {@link SetEnabledProvidersException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public SetEnabledProvidersException(@NonNull String errorType, @Nullable Throwable cause) {
+ this(errorType, null, cause);
+ }
+
+ /**
+ * Constructs a {@link SetEnabledProvidersException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public SetEnabledProvidersException(@NonNull String errorType) {
+ this(errorType, null, null);
+ }
+}
diff --git a/core/java/android/credentials/SetEnabledProvidersRequest.aidl b/core/java/android/credentials/SetEnabledProvidersRequest.aidl
new file mode 100644
index 000000000000..271f58fda532
--- /dev/null
+++ b/core/java/android/credentials/SetEnabledProvidersRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+parcelable SetEnabledProvidersRequest; \ No newline at end of file
diff --git a/core/java/android/credentials/SetEnabledProvidersRequest.java b/core/java/android/credentials/SetEnabledProvidersRequest.java
new file mode 100644
index 000000000000..d1136ba00421
--- /dev/null
+++ b/core/java/android/credentials/SetEnabledProvidersRequest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Sets the enabled list of credential manager providers.
+ *
+ * @hide
+ */
+public final class SetEnabledProvidersRequest implements Parcelable {
+
+ /** List of providers. */
+ @NonNull private final List<String> mProviders;
+
+ /**
+ * Creates a {@link SetEnabledProvidersRequest} with a list of providers. The list is made up of
+ * strings that are flattened component names of the service that is the credman provider.
+ *
+ * @throws NullPointerException If args are null.
+ */
+ public SetEnabledProvidersRequest(@NonNull List<String> providers) {
+ Objects.requireNonNull(providers, "providers must not be null");
+ Preconditions.checkCollectionElementsNotNull(providers, /* valueName= */ "providers");
+ mProviders = providers;
+ }
+
+ private SetEnabledProvidersRequest(@NonNull Parcel in) {
+ mProviders = in.createStringArrayList();
+ }
+
+ public static final @NonNull Creator<SetEnabledProvidersRequest> CREATOR =
+ new Creator<SetEnabledProvidersRequest>() {
+ @Override
+ public SetEnabledProvidersRequest createFromParcel(Parcel in) {
+ return new SetEnabledProvidersRequest(in);
+ }
+
+ @Override
+ public SetEnabledProvidersRequest[] newArray(int size) {
+ return new SetEnabledProvidersRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStringList(mProviders);
+ }
+
+ /** Returns the list of flattened Credential Manager provider component names as strings. */
+ public @NonNull List<String> getProviderComponentNames() {
+ return mProviders;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 2f951ed63bb9..af4e7d72e275 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -20,6 +20,7 @@ import static android.content.Context.CREDENTIAL_SERVICE;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.credentials.ClearCredentialStateRequest;
@@ -30,9 +31,11 @@ import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
+import android.credentials.ISetEnabledProvidersCallback;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.credentials.BeginCreateCredentialRequest;
@@ -208,6 +211,47 @@ public final class CredentialManagerService extends
}
@Override
+ public void setEnabledProviders(
+ List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
+ Log.i(TAG, "setEnabledProviders");
+
+ userId =
+ ActivityManager.handleIncomingUser(
+ Binder.getCallingPid(),
+ Binder.getCallingUid(),
+ userId,
+ false,
+ false,
+ "setEnabledProviders",
+ null);
+
+ String storedValue = String.join(":", providers);
+ if (!Settings.Secure.putStringForUser(
+ getContext().getContentResolver(),
+ Settings.Secure.CREDENTIAL_SERVICE,
+ storedValue,
+ userId)) {
+ Log.e(TAG, "Failed to store setting containing enabled providers");
+ try {
+ callback.onError(
+ "failed_setting_store",
+ "Failed to store setting containing enabled providers");
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue with invoking error response: " + e.getMessage());
+ // TODO: Propagate failure
+ }
+ }
+
+ // Call the callback.
+ try {
+ callback.onResponse();
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue with invoking response: " + e.getMessage());
+ // TODO: Propagate failure
+ }
+ }
+
+ @Override
public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
IClearCredentialStateCallback callback, String callingPackage) {
// TODO: implement.