1 /* 2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.credentials; 18 19 import static android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS; 20 21 import android.annotation.CallbackExecutor; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.app.ActivityOptions; 26 import android.app.PendingIntent; 27 import android.content.Context; 28 import android.content.IntentSender; 29 import android.os.Bundle; 30 import android.os.CancellationSignal; 31 import android.os.OutcomeReceiver; 32 import android.util.Log; 33 34 import java.util.concurrent.Executor; 35 36 37 /** 38 * A response object that prefetches user app credentials and provides metadata about them. It can 39 * then be used to issue the full credential retrieval flow via the 40 * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, 41 * Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection 42 * and credential selection, to officially retrieve a credential. 43 */ 44 public final class PrepareGetCredentialResponse { 45 46 private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic() 47 .setPendingIntentBackgroundActivityStartMode( 48 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle(); 49 50 /** 51 * A handle that represents a pending get-credential operation. Pass this handle to {@link 52 * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, 53 * Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a 54 * credential. 55 */ 56 public static final class PendingGetCredentialHandle { 57 @NonNull 58 private final CredentialManager.GetCredentialTransportPendingUseCase 59 mGetCredentialTransport; 60 /** 61 * The pending intent to be launched to finalize the user credential. If null, the callback 62 * will fail with {@link GetCredentialException#TYPE_NO_CREDENTIAL}. 63 */ 64 @Nullable 65 private final PendingIntent mPendingIntent; 66 67 /** @hide */ PendingGetCredentialHandle( @onNull CredentialManager.GetCredentialTransportPendingUseCase transport, @Nullable PendingIntent pendingIntent)68 PendingGetCredentialHandle( 69 @NonNull CredentialManager.GetCredentialTransportPendingUseCase transport, 70 @Nullable PendingIntent pendingIntent) { 71 mGetCredentialTransport = transport; 72 mPendingIntent = pendingIntent; 73 } 74 75 /** @hide */ show(@onNull Context context, @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback)76 void show(@NonNull Context context, @Nullable CancellationSignal cancellationSignal, 77 @CallbackExecutor @NonNull Executor executor, 78 @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) { 79 if (mPendingIntent == null) { 80 executor.execute(() -> callback.onError( 81 new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL))); 82 return; 83 } 84 85 mGetCredentialTransport.setCallback(new GetPendingCredentialInternalCallback() { 86 @Override 87 public void onPendingIntent(PendingIntent pendingIntent) { 88 try { 89 context.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0, 90 OPTIONS_SENDER_BAL_OPTIN); 91 } catch (IntentSender.SendIntentException e) { 92 Log.e(TAG, "startIntentSender() failed for intent for show()", e); 93 executor.execute(() -> callback.onError( 94 new GetCredentialException(GetCredentialException.TYPE_UNKNOWN))); 95 } 96 } 97 98 @Override 99 public void onResponse(GetCredentialResponse response) { 100 executor.execute(() -> callback.onResult(response)); 101 } 102 103 @Override 104 public void onError(String errorType, String message) { 105 executor.execute( 106 () -> callback.onError(new GetCredentialException(errorType, message))); 107 } 108 }); 109 110 try { 111 context.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0, 112 OPTIONS_SENDER_BAL_OPTIN); 113 } catch (IntentSender.SendIntentException e) { 114 Log.e(TAG, "startIntentSender() failed for intent for show()", e); 115 executor.execute(() -> callback.onError( 116 new GetCredentialException(GetCredentialException.TYPE_UNKNOWN))); 117 } 118 } 119 } 120 private static final String TAG = "CredentialManager"; 121 122 @NonNull private final PrepareGetCredentialResponseInternal mResponseInternal; 123 124 @NonNull private final PendingGetCredentialHandle mPendingGetCredentialHandle; 125 126 /** 127 * Returns true if the user has any candidate credentials for the given {@code credentialType}, 128 * and false otherwise. 129 */ 130 @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) hasCredentialResults(@onNull String credentialType)131 public boolean hasCredentialResults(@NonNull String credentialType) { 132 return mResponseInternal.hasCredentialResults(credentialType); 133 } 134 135 /** 136 * Returns true if the user has any candidate authentication actions (locked credential 137 * supplier), and false otherwise. 138 */ 139 @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) hasAuthenticationResults()140 public boolean hasAuthenticationResults() { 141 return mResponseInternal.hasAuthenticationResults(); 142 } 143 144 /** 145 * Returns true if the user has any candidate remote credential results, and false otherwise. 146 */ 147 @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) hasRemoteResults()148 public boolean hasRemoteResults() { 149 return mResponseInternal.hasRemoteResults(); 150 } 151 152 /** 153 * Returns a handle that represents this pending get-credential operation. Pass this handle to 154 * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, 155 * CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially 156 * retrieve a credential. 157 */ 158 @NonNull getPendingGetCredentialHandle()159 public PendingGetCredentialHandle getPendingGetCredentialHandle() { 160 return mPendingGetCredentialHandle; 161 } 162 163 /** 164 * Constructs a {@link PrepareGetCredentialResponse}. 165 * 166 * @param responseInternal whether caller has the permission to query the credential 167 * result metadata 168 * @param getCredentialTransport the transport for the operation to finalaze a credential 169 * @hide 170 */ PrepareGetCredentialResponse( @onNull PrepareGetCredentialResponseInternal responseInternal, @NonNull CredentialManager.GetCredentialTransportPendingUseCase getCredentialTransport)171 protected PrepareGetCredentialResponse( 172 @NonNull PrepareGetCredentialResponseInternal responseInternal, 173 @NonNull CredentialManager.GetCredentialTransportPendingUseCase 174 getCredentialTransport) { 175 mResponseInternal = responseInternal; 176 mPendingGetCredentialHandle = new PendingGetCredentialHandle( 177 getCredentialTransport, responseInternal.getPendingIntent()); 178 } 179 180 /** @hide */ 181 protected interface GetPendingCredentialInternalCallback { onPendingIntent(@onNull PendingIntent pendingIntent)182 void onPendingIntent(@NonNull PendingIntent pendingIntent); 183 onResponse(@onNull GetCredentialResponse response)184 void onResponse(@NonNull GetCredentialResponse response); 185 onError(@onNull String errorType, @Nullable String message)186 void onError(@NonNull String errorType, @Nullable String message); 187 } 188 } 189