FillResponse.java revision 601d22007488145f8651930d23aeb7a7a95cc591
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.IntDef; 23import android.annotation.NonNull; 24import android.annotation.Nullable; 25import android.annotation.TestApi; 26import android.app.Activity; 27import android.content.IntentSender; 28import android.content.pm.ParceledListSlice; 29import android.os.Bundle; 30import android.os.Parcel; 31import android.os.Parcelable; 32import android.view.autofill.AutofillId; 33import android.widget.RemoteViews; 34 35import com.android.internal.util.Preconditions; 36 37import java.lang.annotation.Retention; 38import java.lang.annotation.RetentionPolicy; 39import java.util.ArrayList; 40import java.util.Arrays; 41import java.util.List; 42 43/** 44 * Response for a {@link 45 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}. 46 * 47 * <p>See the main {@link AutofillService} documentation for more details and examples. 48 */ 49public final class FillResponse implements Parcelable { 50 51 /** 52 * Must be set in the last response to generate 53 * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED} events. 54 */ 55 public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1; 56 57 /** 58 * Used in conjunction to {@link FillResponse.Builder#disableAutofill(long)} to disable autofill 59 * only for the activiy associated with the {@link FillResponse}, instead of the whole app. 60 */ 61 public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2; 62 63 /** @hide */ 64 @IntDef(flag = true, value = { 65 FLAG_TRACK_CONTEXT_COMMITED, 66 FLAG_DISABLE_ACTIVITY_ONLY 67 }) 68 @Retention(RetentionPolicy.SOURCE) 69 @interface FillResponseFlags {} 70 71 private final @Nullable ParceledListSlice<Dataset> mDatasets; 72 private final @Nullable SaveInfo mSaveInfo; 73 private final @Nullable Bundle mClientState; 74 private final @Nullable RemoteViews mPresentation; 75 private final @Nullable IntentSender mAuthentication; 76 private final @Nullable AutofillId[] mAuthenticationIds; 77 private final @Nullable AutofillId[] mIgnoredIds; 78 private final long mDisableDuration; 79 private final @Nullable FieldsDetection mFieldsDetection; 80 private final int mFlags; 81 private int mRequestId; 82 83 private FillResponse(@NonNull Builder builder) { 84 mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null; 85 mSaveInfo = builder.mSaveInfo; 86 mClientState = builder.mClientState; 87 mPresentation = builder.mPresentation; 88 mAuthentication = builder.mAuthentication; 89 mAuthenticationIds = builder.mAuthenticationIds; 90 mIgnoredIds = builder.mIgnoredIds; 91 mDisableDuration = builder.mDisableDuration; 92 mFieldsDetection = builder.mFieldsDetection; 93 mFlags = builder.mFlags; 94 mRequestId = INVALID_REQUEST_ID; 95 } 96 97 /** @hide */ 98 public @Nullable Bundle getClientState() { 99 return mClientState; 100 } 101 102 /** @hide */ 103 public @Nullable List<Dataset> getDatasets() { 104 return (mDatasets != null) ? mDatasets.getList() : null; 105 } 106 107 /** @hide */ 108 public @Nullable SaveInfo getSaveInfo() { 109 return mSaveInfo; 110 } 111 112 /** @hide */ 113 public @Nullable RemoteViews getPresentation() { 114 return mPresentation; 115 } 116 117 /** @hide */ 118 public @Nullable IntentSender getAuthentication() { 119 return mAuthentication; 120 } 121 122 /** @hide */ 123 public @Nullable AutofillId[] getAuthenticationIds() { 124 return mAuthenticationIds; 125 } 126 127 /** @hide */ 128 public @Nullable AutofillId[] getIgnoredIds() { 129 return mIgnoredIds; 130 } 131 132 /** @hide */ 133 public long getDisableDuration() { 134 return mDisableDuration; 135 } 136 137 /** @hide */ 138 public @Nullable FieldsDetection getFieldsDetection() { 139 return mFieldsDetection; 140 } 141 142 /** @hide */ 143 public int getFlags() { 144 return mFlags; 145 } 146 147 /** 148 * Associates a {@link FillResponse} to a request. 149 * 150 * <p>Set inside of the {@link FillCallback} code, not the {@link AutofillService}. 151 * 152 * @param requestId The id of the request to associate the response to. 153 * 154 * @hide 155 */ 156 public void setRequestId(int requestId) { 157 mRequestId = requestId; 158 } 159 160 /** @hide */ 161 public int getRequestId() { 162 return mRequestId; 163 } 164 165 /** 166 * Builder for {@link FillResponse} objects. You must to provide at least 167 * one dataset or set an authentication intent with a presentation view. 168 */ 169 public static final class Builder { 170 private ArrayList<Dataset> mDatasets; 171 private SaveInfo mSaveInfo; 172 private Bundle mClientState; 173 private RemoteViews mPresentation; 174 private IntentSender mAuthentication; 175 private AutofillId[] mAuthenticationIds; 176 private AutofillId[] mIgnoredIds; 177 private long mDisableDuration; 178 private FieldsDetection mFieldsDetection; 179 private int mFlags; 180 private boolean mDestroyed; 181 182 /** 183 * Triggers a custom UI before before autofilling the screen with any data set in this 184 * response. 185 * 186 * <p><b>Note:</b> Although the name of this method suggests that it should be used just for 187 * authentication flow, it can be used for other advanced flows; see {@link AutofillService} 188 * for examples. 189 * 190 * <p>This is typically useful when a user interaction is required to unlock their 191 * data vault if you encrypt the data set labels and data set data. It is recommended 192 * to encrypt only the sensitive data and not the data set labels which would allow 193 * auth on the data set level leading to a better user experience. Note that if you 194 * use sensitive data as a label, for example an email address, then it should also 195 * be encrypted. The provided {@link android.app.PendingIntent intent} must be an 196 * {@link Activity} which implements your authentication flow. Also if you provide an auth 197 * intent you also need to specify the presentation view to be shown in the fill UI 198 * for the user to trigger your authentication flow. 199 * 200 * <p>When a user triggers autofill, the system launches the provided intent 201 * whose extras will have the 202 * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen 203 * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE 204 * client state}. Once you complete your authentication flow you should set the 205 * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and set the 206 * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra 207 * with the fully populated {@link FillResponse response} (or {@code null} if the screen 208 * cannot be autofilled). 209 * 210 * <p>For example, if you provided an empty {@link FillResponse response} because the 211 * user's data was locked and marked that the response needs an authentication then 212 * in the response returned if authentication succeeds you need to provide all 213 * available data sets some of which may need to be further authenticated, for 214 * example a credit card whose CVV needs to be entered. 215 * 216 * <p>If you provide an authentication intent you must also provide a presentation 217 * which is used to visualize visualize the response for triggering the authentication 218 * flow. 219 * 220 * <p><b>Note:</b> Do not make the provided pending intent 221 * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the 222 * platform needs to fill in the authentication arguments. 223 * 224 * @param authentication Intent to an activity with your authentication flow. 225 * @param presentation The presentation to visualize the response. 226 * @param ids id of Views that when focused will display the authentication UI. 227 * 228 * @return This builder. 229 * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if 230 * both {@code authentication} and {@code presentation} are {@code null}, or if 231 * both {@code authentication} and {@code presentation} are non-{@code null} 232 * 233 * @see android.app.PendingIntent#getIntentSender() 234 */ 235 public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids, 236 @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { 237 throwIfDestroyed(); 238 throwIfDisableAutofillCalled(); 239 if (ids == null || ids.length == 0) { 240 throw new IllegalArgumentException("ids cannot be null or empry"); 241 } 242 if (authentication == null ^ presentation == null) { 243 throw new IllegalArgumentException("authentication and presentation" 244 + " must be both non-null or null"); 245 } 246 mAuthentication = authentication; 247 mPresentation = presentation; 248 mAuthenticationIds = ids; 249 return this; 250 } 251 252 /** 253 * Specifies views that should not trigger new 254 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, 255 * FillCallback)} requests. 256 * 257 * <p>This is typically used when the service cannot autofill the view; for example, a 258 * text field representing the result of a Captcha challenge. 259 */ 260 public Builder setIgnoredIds(AutofillId...ids) { 261 throwIfDestroyed(); 262 mIgnoredIds = ids; 263 return this; 264 } 265 266 /** 267 * Adds a new {@link Dataset} to this response. 268 * 269 * <p><b>Note: </b> on Android {@link android.os.Build.VERSION_CODES#O}, the total number of 270 * datasets is limited by the Binder transaction size, so it's recommended to keep it 271 * small (in the range of 10-20 at most) and use pagination by adding a fake 272 * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated dataset} at the end 273 * with a presentation string like "Next 10" that would return a new {@link FillResponse} 274 * with the next 10 datasets, and so on. This limitation was lifted on 275 * Android {@link android.os.Build.VERSION_CODES#O_MR1}, although the Binder transaction 276 * size can still be reached if each dataset itself is too big. 277 * 278 * @return This builder. 279 */ 280 public @NonNull Builder addDataset(@Nullable Dataset dataset) { 281 throwIfDestroyed(); 282 throwIfDisableAutofillCalled(); 283 if (dataset == null) { 284 return this; 285 } 286 if (mDatasets == null) { 287 mDatasets = new ArrayList<>(); 288 } 289 if (!mDatasets.add(dataset)) { 290 return this; 291 } 292 return this; 293 } 294 295 /** 296 * Sets the {@link SaveInfo} associated with this response. 297 * 298 * @return This builder. 299 */ 300 public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) { 301 throwIfDestroyed(); 302 throwIfDisableAutofillCalled(); 303 mSaveInfo = saveInfo; 304 return this; 305 } 306 307 /** 308 * Sets a {@link Bundle state} that will be passed to subsequent APIs that 309 * manipulate this response. For example, they are passed to subsequent 310 * calls to {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, 311 * FillCallback)} and {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}. 312 * You can use this to store intermediate state that is persistent across multiple 313 * fill requests and the subsequent save request. 314 * 315 * <p>If this method is called on multiple {@link FillResponse} objects for the same 316 * screen, just the latest bundle is passed back to the service. 317 * 318 * <p>Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback) 319 * save request} is made the client state is cleared. 320 * 321 * @param clientState The custom client state. 322 * @return This builder. 323 */ 324 public Builder setClientState(@Nullable Bundle clientState) { 325 throwIfDestroyed(); 326 mClientState = clientState; 327 return this; 328 } 329 330 /** 331 * TODO(b/67867469): 332 * - javadoc it 333 * - javadoc how to check results 334 * - unhide 335 * - unhide / remove testApi 336 * - throw exception (and document) if response has datasets or saveinfo 337 * - throw exception (and document) if id on fieldsDetection is ignored 338 * 339 * @hide 340 */ 341 @TestApi 342 public Builder setFieldsDetection(@NonNull FieldsDetection fieldsDetection) { 343 throwIfDestroyed(); 344 throwIfDisableAutofillCalled(); 345 mFieldsDetection = Preconditions.checkNotNull(fieldsDetection); 346 return this; 347 } 348 349 /** 350 * Sets flags changing the response behavior. 351 * 352 * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and 353 * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}. 354 * 355 * @return This builder. 356 */ 357 public Builder setFlags(@FillResponseFlags int flags) { 358 throwIfDestroyed(); 359 mFlags = Preconditions.checkFlagsArgument(flags, 360 FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY); 361 return this; 362 } 363 364 /** 365 * Disables autofill for the app or activity. 366 * 367 * <p>This method is useful to optimize performance in cases where the service knows it 368 * can not autofill an app—for example, when the service has a list of "blacklisted" 369 * apps such as office suites. 370 * 371 * <p>By default, it disables autofill for all activities in the app, unless the response is 372 * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}. 373 * 374 * <p>Autofill for the app or activity is automatically re-enabled after any of the 375 * following conditions: 376 * 377 * <ol> 378 * <li>{@code duration} milliseconds have passed. 379 * <li>The autofill service for the user has changed. 380 * <li>The device has rebooted. 381 * </ol> 382 * 383 * <p><b>Note:</b> Activities that are running when autofill is re-enabled remain 384 * disabled for autofill until they finish and restart. 385 * 386 * @param duration duration to disable autofill, in milliseconds. 387 * 388 * @return this builder 389 * 390 * @throws IllegalArgumentException if {@code duration} is not a positive number. 391 * @throws IllegalStateException if either {@link #addDataset(Dataset)}, 392 * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, or 393 * {@link #setSaveInfo(SaveInfo)} was already called. 394 */ 395 public Builder disableAutofill(long duration) { 396 throwIfDestroyed(); 397 if (duration <= 0) { 398 throw new IllegalArgumentException("duration must be greater than 0"); 399 } 400 if (mAuthentication != null || mDatasets != null || mSaveInfo != null 401 || mFieldsDetection != null) { 402 throw new IllegalStateException("disableAutofill() must be the only method called"); 403 } 404 405 mDisableDuration = duration; 406 return this; 407 } 408 409 /** 410 * Builds a new {@link FillResponse} instance. 411 * 412 * @throws IllegalStateException if any of the following conditions occur: 413 * <ol> 414 * <li>{@link #build()} was already called. 415 * <li>No call was made to {@link #addDataset(Dataset)}, 416 * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, 417 * {@link #setSaveInfo(SaveInfo)}, or {@link #disableAutofill(long)}. 418 * </ol> 419 * 420 * @return A built response. 421 */ 422 public FillResponse build() { 423 throwIfDestroyed(); 424 if (mAuthentication == null && mDatasets == null && mSaveInfo == null 425 && mDisableDuration == 0 && mFieldsDetection == null) { 426 throw new IllegalStateException("need to provide: at least one DataSet, or a " 427 + "SaveInfo, or an authentication with a presentation, " 428 + "or a FieldsDetection, or disable autofill"); 429 } 430 mDestroyed = true; 431 return new FillResponse(this); 432 } 433 434 private void throwIfDestroyed() { 435 if (mDestroyed) { 436 throw new IllegalStateException("Already called #build()"); 437 } 438 } 439 440 private void throwIfDisableAutofillCalled() { 441 if (mDisableDuration > 0) { 442 throw new IllegalStateException("Already called #disableAutofill()"); 443 } 444 } 445 } 446 447 ///////////////////////////////////// 448 // Object "contract" methods. // 449 ///////////////////////////////////// 450 @Override 451 public String toString() { 452 if (!sDebug) return super.toString(); 453 454 // TODO: create a dump() method instead 455 return new StringBuilder( 456 "FillResponse : [mRequestId=" + mRequestId) 457 .append(", datasets=").append(mDatasets == null ? "N/A" : mDatasets.getList()) 458 .append(", saveInfo=").append(mSaveInfo) 459 .append(", clientState=").append(mClientState != null) 460 .append(", hasPresentation=").append(mPresentation != null) 461 .append(", hasAuthentication=").append(mAuthentication != null) 462 .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)) 463 .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds)) 464 .append(", disableDuration=").append(mDisableDuration) 465 .append(", flags=").append(mFlags) 466 .append(", fieldDetection=").append(mFieldsDetection) 467 .append("]") 468 .toString(); 469 } 470 471 ///////////////////////////////////// 472 // Parcelable "contract" methods. // 473 ///////////////////////////////////// 474 475 @Override 476 public int describeContents() { 477 return 0; 478 } 479 480 @Override 481 public void writeToParcel(Parcel parcel, int flags) { 482 parcel.writeParcelable(mDatasets, flags); 483 parcel.writeParcelable(mSaveInfo, flags); 484 parcel.writeParcelable(mClientState, flags); 485 parcel.writeParcelableArray(mAuthenticationIds, flags); 486 parcel.writeParcelable(mAuthentication, flags); 487 parcel.writeParcelable(mPresentation, flags); 488 parcel.writeParcelableArray(mIgnoredIds, flags); 489 parcel.writeLong(mDisableDuration); 490 parcel.writeParcelable(mFieldsDetection, flags); 491 parcel.writeInt(mFlags); 492 parcel.writeInt(mRequestId); 493 } 494 495 public static final Parcelable.Creator<FillResponse> CREATOR = 496 new Parcelable.Creator<FillResponse>() { 497 @Override 498 public FillResponse createFromParcel(Parcel parcel) { 499 // Always go through the builder to ensure the data ingested by 500 // the system obeys the contract of the builder to avoid attacks 501 // using specially crafted parcels. 502 final Builder builder = new Builder(); 503 final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null); 504 final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null; 505 final int datasetCount = (datasets != null) ? datasets.size() : 0; 506 for (int i = 0; i < datasetCount; i++) { 507 builder.addDataset(datasets.get(i)); 508 } 509 builder.setSaveInfo(parcel.readParcelable(null)); 510 builder.setClientState(parcel.readParcelable(null)); 511 512 // Sets authentication state. 513 final AutofillId[] authenticationIds = parcel.readParcelableArray(null, 514 AutofillId.class); 515 final IntentSender authentication = parcel.readParcelable(null); 516 final RemoteViews presentation = parcel.readParcelable(null); 517 if (authenticationIds != null) { 518 builder.setAuthentication(authenticationIds, authentication, presentation); 519 } 520 521 builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class)); 522 final long disableDuration = parcel.readLong(); 523 if (disableDuration > 0) { 524 builder.disableAutofill(disableDuration); 525 } 526 final FieldsDetection fieldsDetection = parcel.readParcelable(null); 527 if (fieldsDetection != null) { 528 builder.setFieldsDetection(fieldsDetection); 529 } 530 builder.setFlags(parcel.readInt()); 531 532 final FillResponse response = builder.build(); 533 response.setRequestId(parcel.readInt()); 534 535 return response; 536 } 537 538 @Override 539 public FillResponse[] newArray(int size) { 540 return new FillResponse[size]; 541 } 542 }; 543} 544