FillResponse.java revision 9f9ee25515591ef33281708c0ab911962f4364a6
1/* 2 * Copyright (C) 2016 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 17package android.service.autofill; 18 19import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; 20import static android.view.autofill.Helper.sDebug; 21 22import android.annotation.NonNull; 23import android.annotation.Nullable; 24import android.content.IntentSender; 25import android.os.Bundle; 26import android.os.Parcel; 27import android.os.Parcelable; 28import android.view.autofill.AutofillId; 29import android.view.autofill.AutofillManager; 30import android.widget.RemoteViews; 31 32import java.util.ArrayList; 33 34/** 35 * Response for a {@link 36 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}. 37 * 38 * <p>The response typically contains one or more {@link Dataset}s, each representing a set of 39 * fields that can be autofilled together, and the Android system displays a dataset picker UI 40 * affordance that the user must use before the {@link android.app.Activity} is filled with 41 * the dataset. 42 * 43 * <p>For example, for a login page with username/password where the user only has one account in 44 * the response could be: 45 * 46 * <pre class="prettyprint"> 47 * new FillResponse.Builder() 48 * .add(new Dataset.Builder(createPresentation()) 49 * .setValue(id1, AutofillValue.forText("homer")) 50 * .setValue(id2, AutofillValue.forText("D'OH!")) 51 * .build()) 52 * .build(); 53 * </pre> 54 * 55 * <p>If the user had 2 accounts, each with its own user-provided names, the response could be: 56 * 57 * <pre class="prettyprint"> 58 * new FillResponse.Builder() 59 * .add(new Dataset.Builder(createFirstPresentation()) 60 * .setValue(id1, AutofillValue.forText("homer")) 61 * .setValue(id2, AutofillValue.forText("D'OH!")) 62 * .build()) 63 * .add(new Dataset.Builder(createSecondPresentation()) 64 * .setValue(id1, AutofillValue.forText("elbarto") 65 * .setValue(id2, AutofillValue.forText("cowabonga") 66 * .build()) 67 * .build(); 68 * </pre> 69 * 70 * If the service is interested on saving the user-edited data back, it must set a {@link SaveInfo} 71 * in the {@link FillResponse}. Typically, the {@link SaveInfo} contains the same ids as the 72 * {@link Dataset}, but other combinations are possible - see {@link SaveInfo} for more details 73 * 74 * <p>If the service has multiple {@link Dataset}s for different sections of the activity, 75 * for example, a user section for which there are two datasets followed by an address 76 * section for which there are two datasets for each user user, then it should "partition" 77 * the activity in sections and populate the response with just a subset of the data that would 78 * fulfill the first section (the name in our example); then once the user fills the first 79 * section and taps a field from the next section (the address in our example), the Android 80 * system would issue another request for that section, and so on. Note that if the user 81 * chooses to populate the first section with a service provided dataset, the subsequent request 82 * would contain the populated values so you don't try to provide suggestions for the first 83 * section but ony for the second one based on the context of what was already filled. For 84 * example, the first response could be: 85 * 86 * <pre class="prettyprint"> 87 * new FillResponse.Builder() 88 * .add(new Dataset.Builder(createFirstPresentation()) 89 * .setValue(id1, AutofillValue.forText("Homer")) 90 * .setValue(id2, AutofillValue.forText("Simpson")) 91 * .build()) 92 * .add(new Dataset.Builder(createSecondPresentation()) 93 * .setValue(id1, AutofillValue.forText("Bart")) 94 * .setValue(id2, AutofillValue.forText("Simpson")) 95 * .build()) 96 * .build(); 97 * </pre> 98 * 99 * <p>Then after the user picks the second dataset and taps the street field to 100 * trigger another autofill request, the second response could be: 101 * 102 * <pre class="prettyprint"> 103 * new FillResponse.Builder() 104 * .add(new Dataset.Builder(createThirdPresentation()) 105 * .setValue(id3, AutofillValue.forText("742 Evergreen Terrace")) 106 * .setValue(id4, AutofillValue.forText("Springfield")) 107 * .build()) 108 * .add(new Dataset.Builder(createFourthPresentation()) 109 * .setValue(id3, AutofillValue.forText("Springfield Power Plant")) 110 * .setValue(id4, AutofillValue.forText("Springfield")) 111 * .build()) 112 * .build(); 113 * </pre> 114 * 115 * <p>The service could require user authentication at the {@link FillResponse} or the 116 * {@link Dataset} level, prior to autofilling an activity - see 117 * {@link FillResponse.Builder#setAuthentication(AutofillId[], IntentSender, RemoteViews)} and 118 * {@link Dataset.Builder#setAuthentication(IntentSender)}. 119 * 120 * <p>It is recommended that you encrypt only the sensitive data but leave the labels unencrypted 121 * which would allow you to provide a dataset presentation views with labels and if the user 122 * chooses one of them challenge the user to authenticate. For example, if the user has a 123 * home and a work address the Home and Work labels could be stored unencrypted as they don't 124 * have any sensitive data while the address data is in an encrypted storage. If the user 125 * chooses Home, then the platform will start your authentication flow. If you encrypt all 126 * data and require auth at the response level the user will have to interact with the fill 127 * UI to trigger a request for the datasets (as they don't see the presentation views for the 128 * possible options) which will start your auth flow and after successfully authenticating 129 * the user will be presented with the Home and Work options to pick one. Hence, you have 130 * flexibility how to implement your auth while storing labels non-encrypted and data 131 * encrypted provides a better user experience. 132 */ 133public final class FillResponse implements Parcelable { 134 135 private final @Nullable ArrayList<Dataset> mDatasets; 136 private final @Nullable SaveInfo mSaveInfo; 137 private final @Nullable Bundle mClientState; 138 private final @Nullable RemoteViews mPresentation; 139 private final @Nullable IntentSender mAuthentication; 140 private final @Nullable AutofillId[] mAuthenticationIds; 141 private final @Nullable AutofillId[] mIgnoredIds; 142 private int mRequestId; 143 144 private FillResponse(@NonNull Builder builder) { 145 mDatasets = builder.mDatasets; 146 mSaveInfo = builder.mSaveInfo; 147 mClientState = builder.mCLientState; 148 mPresentation = builder.mPresentation; 149 mAuthentication = builder.mAuthentication; 150 mAuthenticationIds = builder.mAuthenticationIds; 151 mIgnoredIds = builder.mIgnoredIds; 152 mRequestId = INVALID_REQUEST_ID; 153 } 154 155 /** @hide */ 156 public @Nullable Bundle getClientState() { 157 return mClientState; 158 } 159 160 /** @hide */ 161 public @Nullable ArrayList<Dataset> getDatasets() { 162 return mDatasets; 163 } 164 165 /** @hide */ 166 public @Nullable SaveInfo getSaveInfo() { 167 return mSaveInfo; 168 } 169 170 /** @hide */ 171 public @Nullable RemoteViews getPresentation() { 172 return mPresentation; 173 } 174 175 /** @hide */ 176 public @Nullable IntentSender getAuthentication() { 177 return mAuthentication; 178 } 179 180 /** @hide */ 181 public @Nullable AutofillId[] getAuthenticationIds() { 182 return mAuthenticationIds; 183 } 184 185 /** @hide */ 186 public @Nullable AutofillId[] getIgnoredIds() { 187 return mIgnoredIds; 188 } 189 190 /** 191 * Associates a {@link FillResponse} to a request. 192 * 193 * <p>Set inside of the {@link FillCallback} code, not the {@link AutofillService}. 194 * 195 * @param requestId The id of the request to associate the response to. 196 * 197 * @hide 198 */ 199 public void setRequestId(int requestId) { 200 mRequestId = requestId; 201 } 202 203 /** @hide */ 204 public int getRequestId() { 205 return mRequestId; 206 } 207 208 /** 209 * Builder for {@link FillResponse} objects. You must to provide at least 210 * one dataset or set an authentication intent with a presentation view. 211 */ 212 public static final class Builder { 213 private ArrayList<Dataset> mDatasets; 214 private SaveInfo mSaveInfo; 215 private Bundle mCLientState; 216 private RemoteViews mPresentation; 217 private IntentSender mAuthentication; 218 private AutofillId[] mAuthenticationIds; 219 private AutofillId[] mIgnoredIds; 220 private boolean mDestroyed; 221 222 /** 223 * Requires a fill response authentication before autofilling the activity with 224 * any data set in this response. 225 * 226 * <p>This is typically useful when a user interaction is required to unlock their 227 * data vault if you encrypt the data set labels and data set data. It is recommended 228 * to encrypt only the sensitive data and not the data set labels which would allow 229 * auth on the data set level leading to a better user experience. Note that if you 230 * use sensitive data as a label, for example an email address, then it should also 231 * be encrypted. The provided {@link android.app.PendingIntent intent} must be an 232 * activity which implements your authentication flow. Also if you provide an auth 233 * intent you also need to specify the presentation view to be shown in the fill UI 234 * for the user to trigger your authentication flow. 235 * 236 * <p>When a user triggers autofill, the system launches the provided intent 237 * whose extras will have the {@link AutofillManager#EXTRA_ASSIST_STRUCTURE screen 238 * content}. Once you complete your authentication flow you should set the activity 239 * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated 240 * {@link FillResponse response} by setting it to the {@link 241 * AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. 242 * For example, if you provided an empty {@link FillResponse resppnse} because the 243 * user's data was locked and marked that the response needs an authentication then 244 * in the response returned if authentication succeeds you need to provide all 245 * available data sets some of which may need to be further authenticated, for 246 * example a credit card whose CVV needs to be entered. 247 * 248 * <p>If you provide an authentication intent you must also provide a presentation 249 * which is used to visualize visualize the response for triggering the authentication 250 * flow. 251 * 252 * <p></><strong>Note:</strong> Do not make the provided pending intent 253 * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the 254 * platform needs to fill in the authentication arguments. 255 * 256 * @param authentication Intent to an activity with your authentication flow. 257 * @param presentation The presentation to visualize the response. 258 * @param ids id of Views that when focused will display the authentication UI affordance. 259 * 260 * @return This builder. 261 * @see android.app.PendingIntent#getIntentSender() 262 */ 263 public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids, 264 @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { 265 throwIfDestroyed(); 266 // TODO(b/37424539): assert ids is not null nor empty once old version is removed 267 if (authentication == null ^ presentation == null) { 268 throw new IllegalArgumentException("authentication and presentation" 269 + " must be both non-null or null"); 270 } 271 mAuthentication = authentication; 272 mPresentation = presentation; 273 mAuthenticationIds = ids; 274 return this; 275 } 276 277 /** 278 * TODO(b/37424539): will be removed once clients use the version that takes ids 279 * @hide 280 * @deprecated 281 */ 282 @Deprecated 283 public @NonNull Builder setAuthentication(@Nullable IntentSender authentication, 284 @Nullable RemoteViews presentation) { 285 return setAuthentication(null, authentication, presentation); 286 } 287 288 /** 289 * Specifies views that should not trigger new 290 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, 291 * FillCallback)} requests. 292 * 293 * <p>This is typically used when the service cannot autofill the view; for example, an 294 * {@code EditText} representing a captcha. 295 */ 296 public Builder setIgnoredIds(AutofillId...ids) { 297 mIgnoredIds = ids; 298 return this; 299 } 300 301 /** 302 * Adds a new {@link Dataset} to this response. 303 * 304 * @return This builder. 305 */ 306 public @NonNull Builder addDataset(@Nullable Dataset dataset) { 307 throwIfDestroyed(); 308 if (dataset == null) { 309 return this; 310 } 311 if (mDatasets == null) { 312 mDatasets = new ArrayList<>(); 313 } 314 if (!mDatasets.add(dataset)) { 315 return this; 316 } 317 return this; 318 } 319 320 /** 321 * Sets the {@link SaveInfo} associated with this response. 322 * 323 * <p>See {@link FillResponse} for more info. 324 * 325 * @return This builder. 326 */ 327 public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) { 328 throwIfDestroyed(); 329 mSaveInfo = saveInfo; 330 return this; 331 } 332 333 /** 334 * @deprecated Use {@link #setClientState(Bundle)} instead. 335 * @hide 336 */ 337 @Deprecated 338 public Builder setExtras(@Nullable Bundle extras) { 339 throwIfDestroyed(); 340 mCLientState = extras; 341 return this; 342 } 343 344 /** 345 * Sets a {@link Bundle state} that will be passed to subsequent APIs that 346 * manipulate this response. For example, they are passed to subsequent 347 * calls to {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, 348 * FillCallback)} and {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}. 349 * You can use this to store intermediate state that is persistent across multiple 350 * fill requests and the subsequent save request. 351 * 352 * <p>If this method is called on multiple {@link FillResponse} objects for the same 353 * activity, just the latest bundle is passed back to the service. 354 * 355 * <p>Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback) 356 * save request} is made the client state is cleared. 357 * 358 * @param clientState The custom client state. 359 * @return This builder. 360 */ 361 public Builder setClientState(@Nullable Bundle clientState) { 362 throwIfDestroyed(); 363 mCLientState = clientState; 364 return this; 365 } 366 367 /** 368 * Builds a new {@link FillResponse} instance. You must provide at least 369 * one dataset or some savable ids or an authentication with a presentation 370 * view. 371 * 372 * @return A built response. 373 */ 374 public FillResponse build() { 375 throwIfDestroyed(); 376 377 if (mAuthentication == null && mDatasets == null && mSaveInfo == null) { 378 throw new IllegalArgumentException("need to provide at least one DataSet or a " 379 + "SaveInfo or an authentication with a presentation"); 380 } 381 mDestroyed = true; 382 return new FillResponse(this); 383 } 384 385 private void throwIfDestroyed() { 386 if (mDestroyed) { 387 throw new IllegalStateException("Already called #build()"); 388 } 389 } 390 } 391 392 ///////////////////////////////////// 393 // Object "contract" methods. // 394 ///////////////////////////////////// 395 @Override 396 public String toString() { 397 if (!sDebug) return super.toString(); 398 399 return new StringBuilder( 400 "FillResponse : [mRequestId=" + mRequestId) 401 .append(", datasets=").append(mDatasets) 402 .append(", saveInfo=").append(mSaveInfo) 403 .append(", clientState=").append(mClientState != null) 404 .append(", hasPresentation=").append(mPresentation != null) 405 .append(", hasAuthentication=").append(mAuthentication != null) 406 .append(", authenticationSize=").append(mAuthenticationIds != null 407 ? mAuthenticationIds.length : "N/A") 408 .append(", ignoredIdsSize=").append(mIgnoredIds != null 409 ? mIgnoredIds.length : "N/A") 410 .append("]") 411 .toString(); 412 } 413 414 ///////////////////////////////////// 415 // Parcelable "contract" methods. // 416 ///////////////////////////////////// 417 418 @Override 419 public int describeContents() { 420 return 0; 421 } 422 423 @Override 424 public void writeToParcel(Parcel parcel, int flags) { 425 parcel.writeTypedArrayList(mDatasets, flags); 426 parcel.writeParcelable(mSaveInfo, flags); 427 parcel.writeParcelable(mClientState, flags); 428 parcel.writeParcelableArray(mAuthenticationIds, flags); 429 parcel.writeParcelable(mAuthentication, flags); 430 parcel.writeParcelable(mPresentation, flags); 431 parcel.writeParcelableArray(mIgnoredIds, flags); 432 parcel.writeInt(mRequestId); 433 } 434 435 public static final Parcelable.Creator<FillResponse> CREATOR = 436 new Parcelable.Creator<FillResponse>() { 437 @Override 438 public FillResponse createFromParcel(Parcel parcel) { 439 // Always go through the builder to ensure the data ingested by 440 // the system obeys the contract of the builder to avoid attacks 441 // using specially crafted parcels. 442 final Builder builder = new Builder(); 443 final ArrayList<Dataset> datasets = parcel.readTypedArrayList(null); 444 final int datasetCount = (datasets != null) ? datasets.size() : 0; 445 for (int i = 0; i < datasetCount; i++) { 446 builder.addDataset(datasets.get(i)); 447 } 448 builder.setSaveInfo(parcel.readParcelable(null)); 449 builder.setClientState(parcel.readParcelable(null)); 450 builder.setAuthentication(parcel.readParcelableArray(null, AutofillId.class), 451 parcel.readParcelable(null), parcel.readParcelable(null)); 452 builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class)); 453 final FillResponse response = builder.build(); 454 455 response.setRequestId(parcel.readInt()); 456 457 return response; 458 } 459 460 @Override 461 public FillResponse[] newArray(int size) { 462 return new FillResponse[size]; 463 } 464 }; 465} 466