diff options
9 files changed, 447 insertions, 0 deletions
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index eedb25b1aa8f..408869ec76bc 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -19,6 +19,7 @@ package android.credentials; import static java.util.Objects.requireNonNull; import android.annotation.CallbackExecutor; +import android.annotation.Hide; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -117,6 +118,32 @@ public final class CredentialManager { } /** + * Returns a list of candidate credentials returned from credential manager providers + * + * @param request the request specifying type(s) of credentials to get from the + * credential providers + * @param cancellationSignal an optional signal that allows for cancelling this call + * @param executor the callback will take place on this {@link Executor} + * @param callback the callback invoked when the request succeeds or fails + * + * @hide + */ + @Hide + public void getCandidateCredentials( + @NonNull GetCredentialRequest request, + @Nullable CancellationSignal cancellationSignal, + @CallbackExecutor @NonNull Executor executor, + @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) { + requireNonNull(request, "request must not be null"); + requireNonNull(executor, "executor must not be null"); + requireNonNull(callback, "callback must not be null"); + + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + Log.w(TAG, "getCredential already canceled"); + } + } + + /** * Launches the necessary flows to retrieve an app credential from the user. * * <p>The execution can potentially launch UI flows to collect user consent to using a @@ -641,6 +668,44 @@ public final class CredentialManager { } } + private static class GetCandidateCredentialsTransport + extends IGetCandidateCredentialsCallback.Stub { + + private final Executor mExecutor; + private final OutcomeReceiver<GetCandidateCredentialsResponse, + GetCandidateCredentialsException> mCallback; + + private GetCandidateCredentialsTransport( + Executor executor, + OutcomeReceiver<GetCandidateCredentialsResponse, + GetCandidateCredentialsException> callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onResponse(GetCandidateCredentialsResponse response) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onResult(response)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onError(String errorType, String message) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute( + () -> mCallback.onError(new GetCandidateCredentialsException( + errorType, message))); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + private static class GetCredentialTransport extends IGetCredentialCallback.Stub { // TODO: listen for cancellation to release callback. diff --git a/core/java/android/credentials/GetCandidateCredentialsException.java b/core/java/android/credentials/GetCandidateCredentialsException.java new file mode 100644 index 000000000000..40650d02a93e --- /dev/null +++ b/core/java/android/credentials/GetCandidateCredentialsException.java @@ -0,0 +1,95 @@ +/* + * 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.Hide; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.util.Preconditions; + +/** + * Represents an error encountered during the + * {@link CredentialManager#getCandidateCredentials} operation. + * + * @hide + */ +@Hide +public class GetCandidateCredentialsException extends Exception { + /** + * The error type value for when the given operation failed due to an unknown reason. + */ + @NonNull + public static final String TYPE_UNKNOWN = + "android.credentials.GetCandidateCredentialsException.TYPE_UNKNOWN"; + + /** + * The error type value for when no credential is found available for the given {@link + * CredentialManager#getCandidateCredentials} request. + */ + @NonNull + public static final String TYPE_NO_CREDENTIAL = + "android.credentials.GetCandidateCredentialsException.TYPE_NO_CREDENTIAL"; + + @NonNull + private final String mType; + + /** Returns the specific exception type. */ + @NonNull + public String getType() { + return mType; + } + + /** + * Constructs a {@link GetCandidateCredentialsException}. + * + * @throws IllegalArgumentException If type is empty. + */ + public GetCandidateCredentialsException(@NonNull String type, @Nullable String message) { + this(type, message, null); + } + + /** + * Constructs a {@link GetCandidateCredentialsException}. + * + * @throws IllegalArgumentException If type is empty. + */ + public GetCandidateCredentialsException( + @NonNull String type, @Nullable String message, @Nullable Throwable cause) { + super(message, cause); + this.mType = Preconditions.checkStringNotEmpty(type, + "type must not be empty"); + } + + /** + * Constructs a {@link GetCandidateCredentialsException}. + * + * @throws IllegalArgumentException If type is empty. + */ + public GetCandidateCredentialsException(@NonNull String type, @Nullable Throwable cause) { + this(type, null, cause); + } + + /** + * Constructs a {@link GetCandidateCredentialsException}. + * + * @throws IllegalArgumentException If type is empty. + */ + public GetCandidateCredentialsException(@NonNull String type) { + this(type, null, null); + } +} diff --git a/core/java/android/credentials/GetCandidateCredentialsRequest.aidl b/core/java/android/credentials/GetCandidateCredentialsRequest.aidl new file mode 100644 index 000000000000..d3610894b418 --- /dev/null +++ b/core/java/android/credentials/GetCandidateCredentialsRequest.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 GetCandidateCredentialsRequest;
\ No newline at end of file diff --git a/core/java/android/credentials/GetCandidateCredentialsRequest.java b/core/java/android/credentials/GetCandidateCredentialsRequest.java new file mode 100644 index 000000000000..7f0dcaf060b8 --- /dev/null +++ b/core/java/android/credentials/GetCandidateCredentialsRequest.java @@ -0,0 +1,147 @@ +/* + * 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 static java.util.Objects.requireNonNull; + +import android.annotation.Hide; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.List; + +/** + * A request to retrieve a list of candidate credentials against the list of credential + * options + * + * @hide + */ +@Hide +public final class GetCandidateCredentialsRequest implements Parcelable { + + /** + * The list of credential requests. + */ + @NonNull + private final List<CredentialOption> mCredentialOptions; + + /** + * The top request level data. + */ + @NonNull + private final Bundle mData; + + /** + * The origin of the calling app. Callers of this special API (e.g. browsers) + * can set this origin for an app different from their own, to be able to get credentials + * on behalf of that app. + */ + @Nullable + private String mOrigin; + + /** + * Returns the list of credential options to be requested. + */ + @NonNull + public List<CredentialOption> getCredentialOptions() { + return mCredentialOptions; + } + + /** + * Returns the top request level data. + */ + @NonNull + public Bundle getData() { + return mData; + } + + /** + * Returns the origin of the calling app if set otherwise returns null. + */ + @Nullable + public String getOrigin() { + return mOrigin; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedList(mCredentialOptions, flags); + dest.writeBundle(mData); + dest.writeString8(mOrigin); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "GetCandidateCredentialsRequest {credentialOption=" + mCredentialOptions + + ", data=" + mData + + ", origin=" + mOrigin + + "}"; + } + + private GetCandidateCredentialsRequest(@NonNull List<CredentialOption> credentialOptions, + @NonNull Bundle data, String origin) { + Preconditions.checkCollectionNotEmpty( + credentialOptions, + /*valueName=*/ "credentialOptions"); + Preconditions.checkCollectionElementsNotNull( + credentialOptions, + /*valueName=*/ "credentialOptions"); + mCredentialOptions = credentialOptions; + mData = requireNonNull(data, + "data must not be null"); + mOrigin = origin; + } + + private GetCandidateCredentialsRequest(@NonNull Parcel in) { + List<CredentialOption> credentialOptions = new ArrayList<CredentialOption>(); + in.readTypedList(credentialOptions, CredentialOption.CREATOR); + mCredentialOptions = credentialOptions; + AnnotationValidations.validate(NonNull.class, null, mCredentialOptions); + + Bundle data = in.readBundle(); + mData = data; + AnnotationValidations.validate(NonNull.class, null, mData); + + mOrigin = in.readString8(); + } + + @NonNull + public static final Creator<GetCandidateCredentialsRequest> CREATOR = + new Creator<>() { + @Override + public GetCandidateCredentialsRequest[] newArray(int size) { + return new GetCandidateCredentialsRequest[size]; + } + + @Override + public GetCandidateCredentialsRequest createFromParcel(@NonNull Parcel in) { + return new GetCandidateCredentialsRequest(in); + } + }; +} diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.aidl b/core/java/android/credentials/GetCandidateCredentialsResponse.aidl new file mode 100644 index 000000000000..ffcd3e7078e8 --- /dev/null +++ b/core/java/android/credentials/GetCandidateCredentialsResponse.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 GetCandidateCredentialsResponse;
\ No newline at end of file diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java new file mode 100644 index 000000000000..1d649eb92fde --- /dev/null +++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java @@ -0,0 +1,55 @@ +/* + * 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.Hide; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A list of candidate credentials. + * + * @hide + */ +@Hide +public final class GetCandidateCredentialsResponse implements Parcelable { + // TODO(b/299321990): Add members + protected GetCandidateCredentialsResponse(Parcel in) { + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<GetCandidateCredentialsResponse> CREATOR = + new Creator<GetCandidateCredentialsResponse>() { + @Override + public GetCandidateCredentialsResponse createFromParcel(Parcel in) { + return new GetCandidateCredentialsResponse(in); + } + + @Override + public GetCandidateCredentialsResponse[] newArray(int size) { + return new GetCandidateCredentialsResponse[size]; + } + }; +} diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl index dec729f4a19f..42323d66e533 100644 --- a/core/java/android/credentials/ICredentialManager.aidl +++ b/core/java/android/credentials/ICredentialManager.aidl @@ -21,12 +21,14 @@ import java.util.List; import android.credentials.CredentialProviderInfo; import android.credentials.ClearCredentialStateRequest; import android.credentials.CreateCredentialRequest; +import android.credentials.GetCandidateCredentialsRequest; import android.credentials.GetCredentialRequest; import android.credentials.RegisterCredentialDescriptionRequest; import android.credentials.UnregisterCredentialDescriptionRequest; import android.credentials.IClearCredentialStateCallback; import android.credentials.ICreateCredentialCallback; import android.credentials.IGetCredentialCallback; +import android.credentials.IGetCandidateCredentialsCallback; import android.credentials.IPrepareGetCredentialCallback; import android.credentials.ISetEnabledProvidersCallback; import android.content.ComponentName; @@ -45,6 +47,8 @@ interface ICredentialManager { @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage); + @nullable ICancellationSignal getCandidateCredentials(in GetCandidateCredentialsRequest request, in IGetCandidateCredentialsCallback callback, String callingPackage); + @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage); void setEnabledProviders(in List<String> primaryProviders, in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback); diff --git a/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl b/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl new file mode 100644 index 000000000000..729176a9919d --- /dev/null +++ b/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl @@ -0,0 +1,30 @@ +/* + * 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.app.PendingIntent; +import android.credentials.GetCandidateCredentialsResponse; + +/** + * Listener for a getCandidateCredentials request. + * + * @hide + */ +interface IGetCandidateCredentialsCallback { + oneway void onResponse(in GetCandidateCredentialsResponse response); + oneway void onError(String errorType, String message); +}
\ No newline at end of file diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index d8c684fb8c1e..c544b411b4b1 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -35,11 +35,13 @@ import android.credentials.CreateCredentialException; import android.credentials.CreateCredentialRequest; import android.credentials.CredentialOption; import android.credentials.CredentialProviderInfo; +import android.credentials.GetCandidateCredentialsRequest; import android.credentials.GetCredentialException; import android.credentials.GetCredentialRequest; import android.credentials.IClearCredentialStateCallback; import android.credentials.ICreateCredentialCallback; import android.credentials.ICredentialManager; +import android.credentials.IGetCandidateCredentialsCallback; import android.credentials.IGetCredentialCallback; import android.credentials.IPrepareGetCredentialCallback; import android.credentials.ISetEnabledProvidersCallback; @@ -461,6 +463,17 @@ public final class CredentialManagerService final class CredentialManagerServiceStub extends ICredentialManager.Stub { @Override + public ICancellationSignal getCandidateCredentials( + GetCandidateCredentialsRequest request, + IGetCandidateCredentialsCallback callback, + final String callingPackage) { + Slog.i(TAG, "starting getCandidateCredentials with callingPackage: " + + callingPackage); + // TODO(): Implement + return CancellationSignal.createTransport(); + } + + @Override public ICancellationSignal executeGetCredential( GetCredentialRequest request, IGetCredentialCallback callback, |