From 100bb91e03fb56b5af8644a7d41aa291787cb695 Mon Sep 17 00:00:00 2001 From: Helen Qin Date: Mon, 13 Mar 2023 19:45:54 +0000 Subject: [PATCH] [CredMan] Prefetch credential get api Based on partner feedback, add a new prefetch get-credential api that supports first retrieving credential candidates and delaying the UI flows to finalize a credential to later using a separate api, GetPendingCredentialResponse.show(). Bug: 273308895 Test: local deployment (will add unit test with the unhiding change) Change-Id: I212a6066c71b8468c6d7bfc2df2d9dab737368e0 --- .../credentials/CredentialManager.java | 68 ++++++++++ .../GetPendingCredentialResponse.aidl | 19 +++ .../GetPendingCredentialResponse.java | 127 ++++++++++++++++++ .../credentials/ICredentialManager.aidl | 3 + .../IGetPendingCredentialCallback.aidl | 30 +++++ .../credentials/CredentialManagerService.java | 12 ++ 6 files changed, 259 insertions(+) create mode 100644 core/java/android/credentials/GetPendingCredentialResponse.aidl create mode 100644 core/java/android/credentials/GetPendingCredentialResponse.java create mode 100644 core/java/android/credentials/IGetPendingCredentialCallback.aidl diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index 0806f1db2bb73..493a4ff3c4cf8 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -166,6 +166,48 @@ public final class CredentialManager { } } + /** + * Gets a {@link GetPendingCredentialResponse} that can launch the credential retrieval UI flow + * to request a user credential for your app. + * + * @param request the request specifying type(s) of credentials to get from the user + * @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 + */ + public void getPendingCredential( + @NonNull GetCredentialRequest request, + @Nullable CancellationSignal cancellationSignal, + @CallbackExecutor @NonNull Executor executor, + @NonNull OutcomeReceiver< + GetPendingCredentialResponse, 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, "getPendingCredential already canceled"); + return; + } + + ICancellationSignal cancelRemote = null; + try { + cancelRemote = + mService.executeGetPendingCredential( + request, + new GetPendingCredentialTransport(executor, callback), + mContext.getOpPackageName()); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + if (cancellationSignal != null && cancelRemote != null) { + cancellationSignal.setRemote(cancelRemote); + } + } + /** * Launches the necessary flows to register an app credential for the user. * @@ -442,6 +484,32 @@ public final class CredentialManager { } } + private static class GetPendingCredentialTransport extends IGetPendingCredentialCallback.Stub { + // TODO: listen for cancellation to release callback. + + private final Executor mExecutor; + private final OutcomeReceiver< + GetPendingCredentialResponse, GetCredentialException> mCallback; + + private GetPendingCredentialTransport( + Executor executor, + OutcomeReceiver callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onResponse(GetPendingCredentialResponse response) { + mExecutor.execute(() -> mCallback.onResult(response)); + } + + @Override + public void onError(String errorType, String message) { + mExecutor.execute( + () -> mCallback.onError(new GetCredentialException(errorType, message))); + } + } + private static class GetCredentialTransport extends IGetCredentialCallback.Stub { // TODO: listen for cancellation to release callback. diff --git a/core/java/android/credentials/GetPendingCredentialResponse.aidl b/core/java/android/credentials/GetPendingCredentialResponse.aidl new file mode 100644 index 0000000000000..1cdd637c75112 --- /dev/null +++ b/core/java/android/credentials/GetPendingCredentialResponse.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 GetPendingCredentialResponse; \ No newline at end of file diff --git a/core/java/android/credentials/GetPendingCredentialResponse.java b/core/java/android/credentials/GetPendingCredentialResponse.java new file mode 100644 index 0000000000000..9005d9dfa0310 --- /dev/null +++ b/core/java/android/credentials/GetPendingCredentialResponse.java @@ -0,0 +1,127 @@ +/* + * 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.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Activity; +import android.os.CancellationSignal; +import android.os.OutcomeReceiver; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.concurrent.Executor; + + +/** + * A response object that prefetches user app credentials and provides metadata about them. It can + * then be used to issue the full credential retrieval flow via the + * {@link #show(Activity, CancellationSignal, Executor, OutcomeReceiver)} method to perform the + * necessary flows such as consent collection and officially retrieve a credential. + * + * @hide + */ +public final class GetPendingCredentialResponse implements Parcelable { + private final boolean mHasCredentialResults; + private final boolean mHasAuthenticationResults; + private final boolean mHasRemoteResults; + + /** Returns true if the user has any candidate credentials, and false otherwise. */ + public boolean hasCredentialResults() { + return mHasCredentialResults; + } + + /** + * Returns true if the user has any candidate authentication actions (locked credential + * supplier), and false otherwise. + */ + public boolean hasAuthenticationResults() { + return mHasAuthenticationResults; + } + + /** + * Returns true if the user has any candidate remote credential results, and false otherwise. + */ + public boolean hasRemoteResults() { + return mHasRemoteResults; + } + + /** + * Launches the necessary flows such as consent collection and credential selection to + * officially retrieve a credential among the pending credential candidates. + * + * @param activity the activity used to launch any UI needed + * @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 + */ + public void show(@NonNull Activity activity, @Nullable CancellationSignal cancellationSignal, + @CallbackExecutor @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + // TODO(b/273308895): implement + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBoolean(mHasCredentialResults); + dest.writeBoolean(mHasAuthenticationResults); + dest.writeBoolean(mHasRemoteResults); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "GetCredentialResponse {" + "credential=" + mHasCredentialResults + "}"; + } + + /** + * Constructs a {@link GetPendingCredentialResponse}. + * + * @param hasCredentialResults whether the user has any candidate credentials + * @param hasAuthenticationResults whether the user has any candidate authentication actions + * @param hasRemoteResults whether the user has any candidate remote options + */ + public GetPendingCredentialResponse(boolean hasCredentialResults, + boolean hasAuthenticationResults, boolean hasRemoteResults) { + mHasCredentialResults = hasCredentialResults; + mHasAuthenticationResults = hasAuthenticationResults; + mHasRemoteResults = hasRemoteResults; + } + + private GetPendingCredentialResponse(@NonNull Parcel in) { + mHasCredentialResults = in.readBoolean(); + mHasAuthenticationResults = in.readBoolean(); + mHasRemoteResults = in.readBoolean(); + } + + public static final @NonNull Creator CREATOR = new Creator<>() { + @Override + public GetPendingCredentialResponse[] newArray(int size) { + return new GetPendingCredentialResponse[size]; + } + + @Override + public GetPendingCredentialResponse createFromParcel(@NonNull Parcel in) { + return new GetPendingCredentialResponse(in); + } + }; +} diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl index 8c2cb5aa0d775..af8e7b413ea9d 100644 --- a/core/java/android/credentials/ICredentialManager.aidl +++ b/core/java/android/credentials/ICredentialManager.aidl @@ -27,6 +27,7 @@ import android.credentials.UnregisterCredentialDescriptionRequest; import android.credentials.IClearCredentialStateCallback; import android.credentials.ICreateCredentialCallback; import android.credentials.IGetCredentialCallback; +import android.credentials.IGetPendingCredentialCallback; import android.credentials.ISetEnabledProvidersCallback; import android.content.ComponentName; import android.os.ICancellationSignal; @@ -40,6 +41,8 @@ interface ICredentialManager { @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage); + @nullable ICancellationSignal executeGetPendingCredential(in GetCredentialRequest request, in IGetPendingCredentialCallback callback, String callingPackage); + @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage); @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage); diff --git a/core/java/android/credentials/IGetPendingCredentialCallback.aidl b/core/java/android/credentials/IGetPendingCredentialCallback.aidl new file mode 100644 index 0000000000000..4ab0f998adaee --- /dev/null +++ b/core/java/android/credentials/IGetPendingCredentialCallback.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.GetPendingCredentialResponse; + +/** + * Listener for a executeGetPendingCredential request. + * + * @hide + */ +interface IGetPendingCredentialCallback { + oneway void onResponse(in GetPendingCredentialResponse 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 10d3dc030362b..4c5c366b6a82c 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -41,6 +41,7 @@ import android.credentials.IClearCredentialStateCallback; import android.credentials.ICreateCredentialCallback; import android.credentials.ICredentialManager; import android.credentials.IGetCredentialCallback; +import android.credentials.IGetPendingCredentialCallback; import android.credentials.ISetEnabledProvidersCallback; import android.credentials.RegisterCredentialDescriptionRequest; import android.credentials.UnregisterCredentialDescriptionRequest; @@ -438,6 +439,17 @@ public final class CredentialManagerService return cancelTransport; } + @Override + public ICancellationSignal executeGetPendingCredential( + GetCredentialRequest request, + IGetPendingCredentialCallback callback, + final String callingPackage) { + // TODO(b/273308895): implement + + ICancellationSignal cancelTransport = CancellationSignal.createTransport(); + return cancelTransport; + } + private void processGetCredential( GetCredentialRequest request, IGetCredentialCallback callback,