1 /* 2 * Copyright (C) 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.service.credentials; 18 19 import android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.content.pm.ParceledListSlice; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 27 import com.android.internal.util.Preconditions; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 import java.util.Objects; 32 33 /** 34 * Response from a credential provider, containing credential entries and other associated 35 * data to be shown on the account selector UI. 36 */ 37 public final class BeginGetCredentialResponse implements Parcelable { 38 /** List of credential entries to be displayed on the UI. */ 39 private final @NonNull ParceledListSlice<CredentialEntry> mCredentialEntries; 40 41 /** List of authentication entries to be displayed on the UI. */ 42 private final @NonNull ParceledListSlice<Action> mAuthenticationEntries; 43 44 /** List of provider actions to be displayed on the UI. */ 45 private final @NonNull ParceledListSlice<Action> mActions; 46 47 /** Remote credential entry to get the response from a different device. */ 48 private final @Nullable RemoteEntry mRemoteCredentialEntry; 49 50 /** 51 * Creates an empty response instance, to be used when there are no {@link CredentialEntry}, 52 * or {@link Action} to return. 53 */ BeginGetCredentialResponse()54 public BeginGetCredentialResponse() { 55 this(/*credentialEntries=*/new ParceledListSlice<>(new ArrayList<>()), 56 /*authenticationEntries=*/new ParceledListSlice<>(new ArrayList<>()), 57 /*actions=*/new ParceledListSlice<>(new ArrayList<>()), 58 /*remoteCredentialEntry=*/null); 59 } 60 BeginGetCredentialResponse( @onNull ParceledListSlice<CredentialEntry> credentialEntries, @NonNull ParceledListSlice<Action> authenticationEntries, @NonNull ParceledListSlice<Action> actions, @Nullable RemoteEntry remoteCredentialEntry)61 private BeginGetCredentialResponse( 62 @NonNull ParceledListSlice<CredentialEntry> credentialEntries, 63 @NonNull ParceledListSlice<Action> authenticationEntries, 64 @NonNull ParceledListSlice<Action> actions, 65 @Nullable RemoteEntry remoteCredentialEntry) { 66 mCredentialEntries = credentialEntries; 67 mAuthenticationEntries = authenticationEntries; 68 mActions = actions; 69 mRemoteCredentialEntry = remoteCredentialEntry; 70 } 71 BeginGetCredentialResponse(@onNull Parcel in)72 private BeginGetCredentialResponse(@NonNull Parcel in) { 73 mCredentialEntries = in.readParcelable( 74 null, android.content.pm.ParceledListSlice.class); 75 mAuthenticationEntries = in.readParcelable( 76 null, android.content.pm.ParceledListSlice.class); 77 mActions = in.readParcelable( 78 null, android.content.pm.ParceledListSlice.class); 79 mRemoteCredentialEntry = in.readTypedObject(RemoteEntry.CREATOR); 80 } 81 82 public static final @NonNull Creator<BeginGetCredentialResponse> CREATOR = 83 new Creator<BeginGetCredentialResponse>() { 84 @Override 85 public BeginGetCredentialResponse createFromParcel(@NonNull Parcel in) { 86 return new BeginGetCredentialResponse(in); 87 } 88 89 @Override 90 public BeginGetCredentialResponse[] newArray(int size) { 91 return new BeginGetCredentialResponse[size]; 92 } 93 }; 94 95 @Override describeContents()96 public int describeContents() { 97 return 0; 98 } 99 100 @Override writeToParcel(@onNull Parcel dest, int flags)101 public void writeToParcel(@NonNull Parcel dest, int flags) { 102 dest.writeParcelable(mCredentialEntries, flags); 103 dest.writeParcelable(mAuthenticationEntries, flags); 104 dest.writeParcelable(mActions, flags); 105 dest.writeTypedObject(mRemoteCredentialEntry, flags); 106 } 107 108 /** 109 * Returns the list of credential entries to be displayed on the UI. 110 */ getCredentialEntries()111 public @NonNull List<CredentialEntry> getCredentialEntries() { 112 return mCredentialEntries.getList(); 113 } 114 115 /** 116 * Returns the list of authentication entries to be displayed on the UI. 117 */ getAuthenticationActions()118 public @NonNull List<Action> getAuthenticationActions() { 119 return mAuthenticationEntries.getList(); 120 } 121 122 /** 123 * Returns the list of actions to be displayed on the UI. 124 */ getActions()125 public @NonNull List<Action> getActions() { 126 127 return mActions.getList(); 128 } 129 130 /** 131 * Returns the remote credential entry to be displayed on the UI. 132 */ getRemoteCredentialEntry()133 public @Nullable RemoteEntry getRemoteCredentialEntry() { 134 return mRemoteCredentialEntry; 135 } 136 137 /** 138 * Builds an instance of {@link BeginGetCredentialResponse}. 139 */ 140 public static final class Builder { 141 private List<CredentialEntry> mCredentialEntries = new ArrayList<>(); 142 143 private List<Action> mAuthenticationEntries = new ArrayList<>(); 144 private List<Action> mActions = new ArrayList<>(); 145 private RemoteEntry mRemoteCredentialEntry; 146 147 /** 148 * Sets a remote credential entry to be shown on the UI. Provider must set this if they 149 * wish to get the credential from a different device. 150 * 151 * <p> When constructing the {@link CredentialEntry} object, the {@code pendingIntent} 152 * must be set such that it leads to an activity that can provide UI to fulfill the request 153 * on a remote device. When user selects this {@code remoteCredentialEntry}, the system will 154 * invoke the {@code pendingIntent} set on the {@link CredentialEntry}. 155 * 156 * <p> Once the remote credential flow is complete, the {@link android.app.Activity} 157 * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the 158 * {@link CredentialProviderService#EXTRA_GET_CREDENTIAL_RESPONSE} key should be populated 159 * with a {@link android.credentials.Credential} object. 160 * 161 * <p> Note that as a provider service you will only be able to set a remote entry if : 162 * - Provider service possesses the 163 * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission. 164 * - Provider service is configured as the provider that can provide remote entries. 165 * 166 * If the above conditions are not met, setting back {@link BeginGetCredentialResponse} 167 * on the callback from {@link CredentialProviderService#onBeginGetCredential} will 168 * throw a {@link SecurityException}. 169 */ 170 @RequiresPermission(Manifest.permission.PROVIDE_REMOTE_CREDENTIALS) setRemoteCredentialEntry(@ullable RemoteEntry remoteCredentialEntry)171 public @NonNull Builder setRemoteCredentialEntry(@Nullable RemoteEntry 172 remoteCredentialEntry) { 173 mRemoteCredentialEntry = remoteCredentialEntry; 174 return this; 175 } 176 177 /** 178 * Adds a {@link CredentialEntry} to the list of entries to be displayed on 179 * the UI. 180 * 181 * @throws NullPointerException If the {@code credentialEntry} is null. 182 */ addCredentialEntry(@onNull CredentialEntry credentialEntry)183 public @NonNull Builder addCredentialEntry(@NonNull CredentialEntry credentialEntry) { 184 mCredentialEntries.add(Objects.requireNonNull(credentialEntry)); 185 return this; 186 } 187 188 /** 189 * Add an authentication entry to be shown on the UI. Providers must set this entry if 190 * the corresponding account is locked and no underlying credentials can be returned. 191 * 192 * <p> When the user selects this {@code authenticationAction}, the system invokes the 193 * corresponding {@code pendingIntent}. 194 * Once the authentication action activity is launched, and the user is authenticated, 195 * providers should create another response with {@link BeginGetCredentialResponse} using 196 * this time adding the unlocked credentials in the form of {@link CredentialEntry}'s. 197 * 198 * <p>The new response object must be set on the authentication activity's 199 * result. The result code should be set to {@link android.app.Activity#RESULT_OK} and 200 * the {@link CredentialProviderService#EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE} extra 201 * should be set with the new fully populated {@link BeginGetCredentialResponse} object. 202 * 203 * @throws NullPointerException If {@code authenticationAction} is null. 204 */ addAuthenticationAction(@onNull Action authenticationAction)205 public @NonNull Builder addAuthenticationAction(@NonNull Action authenticationAction) { 206 mAuthenticationEntries.add(Objects.requireNonNull(authenticationAction)); 207 return this; 208 } 209 210 /** 211 * Adds an {@link Action} to the list of actions to be displayed on 212 * the UI. 213 * 214 * <p> An {@code action} must be used for independent user actions, 215 * such as opening the app, intenting directly into a certain app activity etc. The 216 * {@code pendingIntent} set with the {@code action} must invoke the corresponding 217 * activity. 218 * 219 * @throws NullPointerException If {@code action} is null. 220 */ addAction(@onNull Action action)221 public @NonNull Builder addAction(@NonNull Action action) { 222 mActions.add(Objects.requireNonNull(action, "action must not be null")); 223 return this; 224 } 225 226 /** 227 * Sets the list of actions to be displayed on the UI. 228 * 229 * @throws NullPointerException If {@code actions} is null, or any of its elements 230 * is null. 231 */ setActions(@onNull List<Action> actions)232 public @NonNull Builder setActions(@NonNull List<Action> actions) { 233 mActions = Preconditions.checkCollectionElementsNotNull(actions, 234 "actions"); 235 return this; 236 } 237 238 /** 239 * Sets the list of credential entries to be displayed on the 240 * account selector UI. 241 * 242 * @throws NullPointerException If {@code credentialEntries} is null, or any of its 243 * elements is null. 244 */ setCredentialEntries( @onNull List<CredentialEntry> credentialEntries)245 public @NonNull Builder setCredentialEntries( 246 @NonNull List<CredentialEntry> credentialEntries) { 247 mCredentialEntries = Preconditions.checkCollectionElementsNotNull( 248 credentialEntries, 249 "credentialEntries"); 250 return this; 251 } 252 253 /** 254 * Sets the list of authentication entries to be displayed on the 255 * account selector UI. 256 * 257 * @throws NullPointerException If {@code authenticationEntries} is null, or any of its 258 * elements is null. 259 */ setAuthenticationActions( @onNull List<Action> authenticationActions)260 public @NonNull Builder setAuthenticationActions( 261 @NonNull List<Action> authenticationActions) { 262 mAuthenticationEntries = Preconditions.checkCollectionElementsNotNull( 263 authenticationActions, 264 "authenticationActions"); 265 return this; 266 } 267 268 /** 269 * Builds a {@link BeginGetCredentialResponse} instance. 270 */ build()271 public @NonNull BeginGetCredentialResponse build() { 272 return new BeginGetCredentialResponse( 273 new ParceledListSlice<>(mCredentialEntries), 274 new ParceledListSlice<>(mAuthenticationEntries), 275 new ParceledListSlice<>(mActions), 276 mRemoteCredentialEntry); 277 } 278 } 279 } 280