/* * 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 android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.app.ActivityOptions; import android.app.PendingIntent; import android.content.Context; import android.content.IntentSender; import android.os.Bundle; import android.os.CancellationSignal; import android.os.OutcomeReceiver; import android.util.Log; 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 CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, * Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection * and credential selection, to officially retrieve a credential. */ public final class PrepareGetCredentialResponse { private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic() .setPendingIntentBackgroundActivityStartMode( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle(); /** * A handle that represents a pending get-credential operation. Pass this handle to {@link * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, * Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a * credential. */ public static final class PendingGetCredentialHandle { @NonNull private final CredentialManager.GetCredentialTransportPendingUseCase mGetCredentialTransport; /** * The pending intent to be launched to finalize the user credential. If null, the callback * will fail with {@link GetCredentialException#TYPE_NO_CREDENTIAL}. */ @Nullable private final PendingIntent mPendingIntent; /** @hide */ PendingGetCredentialHandle( @NonNull CredentialManager.GetCredentialTransportPendingUseCase transport, @Nullable PendingIntent pendingIntent) { mGetCredentialTransport = transport; mPendingIntent = pendingIntent; } /** @hide */ void show(@NonNull Context context, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver callback) { if (mPendingIntent == null) { executor.execute(() -> callback.onError( new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL))); return; } mGetCredentialTransport.setCallback(new GetPendingCredentialInternalCallback() { @Override public void onPendingIntent(PendingIntent pendingIntent) { try { context.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0, OPTIONS_SENDER_BAL_OPTIN); } catch (IntentSender.SendIntentException e) { Log.e(TAG, "startIntentSender() failed for intent for show()", e); executor.execute(() -> callback.onError( new GetCredentialException(GetCredentialException.TYPE_UNKNOWN))); } } @Override public void onResponse(GetCredentialResponse response) { executor.execute(() -> callback.onResult(response)); } @Override public void onError(String errorType, String message) { executor.execute( () -> callback.onError(new GetCredentialException(errorType, message))); } }); try { context.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0, OPTIONS_SENDER_BAL_OPTIN); } catch (IntentSender.SendIntentException e) { Log.e(TAG, "startIntentSender() failed for intent for show()", e); executor.execute(() -> callback.onError( new GetCredentialException(GetCredentialException.TYPE_UNKNOWN))); } } } private static final String TAG = "CredentialManager"; @NonNull private final PrepareGetCredentialResponseInternal mResponseInternal; @NonNull private final PendingGetCredentialHandle mPendingGetCredentialHandle; /** * Returns true if the user has any candidate credentials for the given {@code credentialType}, * and false otherwise. */ @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasCredentialResults(@NonNull String credentialType) { return mResponseInternal.hasCredentialResults(credentialType); } /** * Returns true if the user has any candidate authentication actions (locked credential * supplier), and false otherwise. */ @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasAuthenticationResults() { return mResponseInternal.hasAuthenticationResults(); } /** * Returns true if the user has any candidate remote credential results, and false otherwise. */ @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasRemoteResults() { return mResponseInternal.hasRemoteResults(); } /** * Returns a handle that represents this pending get-credential operation. Pass this handle to * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, * CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially * retrieve a credential. */ @NonNull public PendingGetCredentialHandle getPendingGetCredentialHandle() { return mPendingGetCredentialHandle; } /** * Constructs a {@link PrepareGetCredentialResponse}. * * @param responseInternal whether caller has the permission to query the credential * result metadata * @param getCredentialTransport the transport for the operation to finalaze a credential * @hide */ protected PrepareGetCredentialResponse( @NonNull PrepareGetCredentialResponseInternal responseInternal, @NonNull CredentialManager.GetCredentialTransportPendingUseCase getCredentialTransport) { mResponseInternal = responseInternal; mPendingGetCredentialHandle = new PendingGetCredentialHandle( getCredentialTransport, responseInternal.getPendingIntent()); } /** @hide */ protected interface GetPendingCredentialInternalCallback { void onPendingIntent(@NonNull PendingIntent pendingIntent); void onResponse(@NonNull GetCredentialResponse response); void onError(@NonNull String errorType, @Nullable String message); } }