diff options
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. |