[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
This commit is contained in:
Helen Qin
2023-03-13 19:45:54 +00:00
parent 84962b9348
commit 100bb91e03
6 changed files with 259 additions and 0 deletions

View File

@@ -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<GetPendingCredentialResponse, GetCredentialException> 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.

View File

@@ -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;

View File

@@ -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<GetCredentialResponse, GetCredentialException> 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<GetPendingCredentialResponse> CREATOR = new Creator<>() {
@Override
public GetPendingCredentialResponse[] newArray(int size) {
return new GetPendingCredentialResponse[size];
}
@Override
public GetPendingCredentialResponse createFromParcel(@NonNull Parcel in) {
return new GetPendingCredentialResponse(in);
}
};
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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,