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 17 package android.service.autofill; 18 19 import static android.service.autofill.AutofillServiceHelper.assertValid; 20 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; 21 import static android.view.autofill.Helper.sDebug; 22 23 import android.annotation.IntDef; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.SuppressLint; 27 import android.annotation.TestApi; 28 import android.app.Activity; 29 import android.content.IntentSender; 30 import android.content.pm.ParceledListSlice; 31 import android.os.Bundle; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.view.autofill.AutofillId; 35 import android.widget.RemoteViews; 36 37 import com.android.internal.util.Preconditions; 38 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.List; 44 45 /** 46 * Response for an {@link 47 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}. 48 * 49 * <p>See the main {@link AutofillService} documentation for more details and examples. 50 */ 51 public final class FillResponse implements Parcelable { 52 53 /** 54 * Flag used to generate {@link FillEventHistory.Event events} of type 55 * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}—if this flag is not passed to 56 * {@link Builder#setFlags(int)}, these events are not generated. 57 */ 58 public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1; 59 60 /** 61 * Flag used to change the behavior of {@link FillResponse.Builder#disableAutofill(long)}— 62 * when this flag is passed to {@link Builder#setFlags(int)}, autofill is disabled only for the 63 * activiy that generated the {@link FillRequest}, not the whole app. 64 */ 65 public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2; 66 67 /** @hide */ 68 @IntDef(flag = true, prefix = { "FLAG_" }, value = { 69 FLAG_TRACK_CONTEXT_COMMITED, 70 FLAG_DISABLE_ACTIVITY_ONLY 71 }) 72 @Retention(RetentionPolicy.SOURCE) 73 @interface FillResponseFlags {} 74 75 private final @Nullable ParceledListSlice<Dataset> mDatasets; 76 private final @Nullable SaveInfo mSaveInfo; 77 private final @Nullable Bundle mClientState; 78 private final @Nullable RemoteViews mPresentation; 79 private final @Nullable InlinePresentation mInlinePresentation; 80 private final @Nullable InlinePresentation mInlineTooltipPresentation; 81 private final @Nullable RemoteViews mHeader; 82 private final @Nullable RemoteViews mFooter; 83 private final @Nullable IntentSender mAuthentication; 84 private final @Nullable AutofillId[] mAuthenticationIds; 85 private final @Nullable AutofillId[] mIgnoredIds; 86 private final long mDisableDuration; 87 private final @Nullable AutofillId[] mFieldClassificationIds; 88 private final int mFlags; 89 private int mRequestId; 90 private final @Nullable UserData mUserData; 91 private final @Nullable int[] mCancelIds; 92 private final boolean mSupportsInlineSuggestions; 93 FillResponse(@onNull Builder builder)94 private FillResponse(@NonNull Builder builder) { 95 mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null; 96 mSaveInfo = builder.mSaveInfo; 97 mClientState = builder.mClientState; 98 mPresentation = builder.mPresentation; 99 mInlinePresentation = builder.mInlinePresentation; 100 mInlineTooltipPresentation = builder.mInlineTooltipPresentation; 101 mHeader = builder.mHeader; 102 mFooter = builder.mFooter; 103 mAuthentication = builder.mAuthentication; 104 mAuthenticationIds = builder.mAuthenticationIds; 105 mIgnoredIds = builder.mIgnoredIds; 106 mDisableDuration = builder.mDisableDuration; 107 mFieldClassificationIds = builder.mFieldClassificationIds; 108 mFlags = builder.mFlags; 109 mRequestId = INVALID_REQUEST_ID; 110 mUserData = builder.mUserData; 111 mCancelIds = builder.mCancelIds; 112 mSupportsInlineSuggestions = builder.mSupportsInlineSuggestions; 113 } 114 115 /** @hide */ getClientState()116 public @Nullable Bundle getClientState() { 117 return mClientState; 118 } 119 120 /** @hide */ getDatasets()121 public @Nullable List<Dataset> getDatasets() { 122 return (mDatasets != null) ? mDatasets.getList() : null; 123 } 124 125 /** @hide */ getSaveInfo()126 public @Nullable SaveInfo getSaveInfo() { 127 return mSaveInfo; 128 } 129 130 /** @hide */ getPresentation()131 public @Nullable RemoteViews getPresentation() { 132 return mPresentation; 133 } 134 135 /** @hide */ getInlinePresentation()136 public @Nullable InlinePresentation getInlinePresentation() { 137 return mInlinePresentation; 138 } 139 140 /** @hide */ getInlineTooltipPresentation()141 public @Nullable InlinePresentation getInlineTooltipPresentation() { 142 return mInlineTooltipPresentation; 143 } 144 145 /** @hide */ getHeader()146 public @Nullable RemoteViews getHeader() { 147 return mHeader; 148 } 149 150 /** @hide */ getFooter()151 public @Nullable RemoteViews getFooter() { 152 return mFooter; 153 } 154 155 /** @hide */ getAuthentication()156 public @Nullable IntentSender getAuthentication() { 157 return mAuthentication; 158 } 159 160 /** @hide */ getAuthenticationIds()161 public @Nullable AutofillId[] getAuthenticationIds() { 162 return mAuthenticationIds; 163 } 164 165 /** @hide */ getIgnoredIds()166 public @Nullable AutofillId[] getIgnoredIds() { 167 return mIgnoredIds; 168 } 169 170 /** @hide */ getDisableDuration()171 public long getDisableDuration() { 172 return mDisableDuration; 173 } 174 175 /** @hide */ getFieldClassificationIds()176 public @Nullable AutofillId[] getFieldClassificationIds() { 177 return mFieldClassificationIds; 178 } 179 180 /** @hide */ getUserData()181 public @Nullable UserData getUserData() { 182 return mUserData; 183 } 184 185 /** @hide */ 186 @TestApi getFlags()187 public int getFlags() { 188 return mFlags; 189 } 190 191 /** 192 * Associates a {@link FillResponse} to a request. 193 * 194 * <p>Set inside of the {@link FillCallback} code, not the {@link AutofillService}. 195 * 196 * @param requestId The id of the request to associate the response to. 197 * 198 * @hide 199 */ setRequestId(int requestId)200 public void setRequestId(int requestId) { 201 mRequestId = requestId; 202 } 203 204 /** @hide */ getRequestId()205 public int getRequestId() { 206 return mRequestId; 207 } 208 209 /** @hide */ 210 @Nullable getCancelIds()211 public int[] getCancelIds() { 212 return mCancelIds; 213 } 214 215 /** @hide */ supportsInlineSuggestions()216 public boolean supportsInlineSuggestions() { 217 return mSupportsInlineSuggestions; 218 } 219 220 /** 221 * Builder for {@link FillResponse} objects. You must to provide at least 222 * one dataset or set an authentication intent with a presentation view. 223 */ 224 public static final class Builder { 225 private ArrayList<Dataset> mDatasets; 226 private SaveInfo mSaveInfo; 227 private Bundle mClientState; 228 private RemoteViews mPresentation; 229 private InlinePresentation mInlinePresentation; 230 private InlinePresentation mInlineTooltipPresentation; 231 private RemoteViews mHeader; 232 private RemoteViews mFooter; 233 private IntentSender mAuthentication; 234 private AutofillId[] mAuthenticationIds; 235 private AutofillId[] mIgnoredIds; 236 private long mDisableDuration; 237 private AutofillId[] mFieldClassificationIds; 238 private int mFlags; 239 private boolean mDestroyed; 240 private UserData mUserData; 241 private int[] mCancelIds; 242 private boolean mSupportsInlineSuggestions; 243 244 /** 245 * Triggers a custom UI before before autofilling the screen with any data set in this 246 * response. 247 * 248 * <p><b>Note:</b> Although the name of this method suggests that it should be used just for 249 * authentication flow, it can be used for other advanced flows; see {@link AutofillService} 250 * for examples. 251 * 252 * <p>This is typically useful when a user interaction is required to unlock their 253 * data vault if you encrypt the data set labels and data set data. It is recommended 254 * to encrypt only the sensitive data and not the data set labels which would allow 255 * auth on the data set level leading to a better user experience. Note that if you 256 * use sensitive data as a label, for example an email address, then it should also 257 * be encrypted. The provided {@link android.app.PendingIntent intent} must be an 258 * {@link Activity} which implements your authentication flow. Also if you provide an auth 259 * intent you also need to specify the presentation view to be shown in the fill UI 260 * for the user to trigger your authentication flow. 261 * 262 * <p>When a user triggers autofill, the system launches the provided intent 263 * whose extras will have the 264 * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen 265 * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE 266 * client state}. Once you complete your authentication flow you should set the 267 * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and set the 268 * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra 269 * with the fully populated {@link FillResponse response} (or {@code null} if the screen 270 * cannot be autofilled). 271 * 272 * <p>For example, if you provided an empty {@link FillResponse response} because the 273 * user's data was locked and marked that the response needs an authentication then 274 * in the response returned if authentication succeeds you need to provide all 275 * available data sets some of which may need to be further authenticated, for 276 * example a credit card whose CVV needs to be entered. 277 * 278 * <p>If you provide an authentication intent you must also provide a presentation 279 * which is used to visualize visualize the response for triggering the authentication 280 * flow. 281 * 282 * <p><b>Note:</b> Do not make the provided pending intent 283 * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the 284 * platform needs to fill in the authentication arguments. 285 * 286 * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color 287 * or background color: Autofill on different platforms may have different themes. 288 * 289 * @param authentication Intent to an activity with your authentication flow. 290 * @param presentation The presentation to visualize the response. 291 * @param ids id of Views that when focused will display the authentication UI. 292 * 293 * @return This builder. 294 * 295 * @throws IllegalArgumentException if any of the following occurs: 296 * <ul> 297 * <li>{@code ids} is {@code null}</li> 298 * <li>{@code ids} is empty</li> 299 * <li>{@code ids} contains a {@code null} element</li> 300 * <li>both {@code authentication} and {@code presentation} are {@code null}</li> 301 * <li>both {@code authentication} and {@code presentation} are non-{@code null}</li> 302 * </ul> 303 * 304 * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a 305 * {@link #setFooter(RemoteViews) footer} are already set for this builder. 306 * 307 * @see android.app.PendingIntent#getIntentSender() 308 */ 309 @NonNull setAuthentication(@onNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation)310 public Builder setAuthentication(@NonNull AutofillId[] ids, 311 @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { 312 throwIfDestroyed(); 313 throwIfDisableAutofillCalled(); 314 if (mHeader != null || mFooter != null) { 315 throw new IllegalStateException("Already called #setHeader() or #setFooter()"); 316 } 317 318 if (authentication == null ^ presentation == null) { 319 throw new IllegalArgumentException("authentication and presentation" 320 + " must be both non-null or null"); 321 } 322 mAuthentication = authentication; 323 mPresentation = presentation; 324 mAuthenticationIds = assertValid(ids); 325 return this; 326 } 327 328 /** 329 * Triggers a custom UI before before autofilling the screen with any data set in this 330 * response. 331 * 332 * <p><b>Note:</b> Although the name of this method suggests that it should be used just for 333 * authentication flow, it can be used for other advanced flows; see {@link AutofillService} 334 * for examples. 335 * 336 * <p>This method is similar to 337 * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, but also accepts 338 * an {@link InlinePresentation} presentation which is required for authenticating through 339 * the inline autofill flow. 340 * 341 * <p><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does 342 * not work with {@link InlinePresentation}.</p> 343 * 344 * @param authentication Intent to an activity with your authentication flow. 345 * @param presentation The presentation to visualize the response. 346 * @param inlinePresentation The inlinePresentation to visualize the response inline. 347 * @param ids id of Views that when focused will display the authentication UI. 348 * 349 * @return This builder. 350 * 351 * @throws IllegalArgumentException if any of the following occurs: 352 * <ul> 353 * <li>{@code ids} is {@code null}</li> 354 * <li>{@code ids} is empty</li> 355 * <li>{@code ids} contains a {@code null} element</li> 356 * <li>both {@code authentication} and {@code presentation} are {@code null}</li> 357 * <li>both {@code authentication} and {@code presentation} are non-{@code null}</li> 358 * <li>both {@code authentication} and {@code inlinePresentation} are {@code null}</li> 359 * <li>both {@code authentication} and {@code inlinePresentation} are 360 * non-{@code null}</li> 361 * </ul> 362 * 363 * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a 364 * {@link #setFooter(RemoteViews) footer} are already set for this builder. 365 * 366 * @see android.app.PendingIntent#getIntentSender() 367 */ 368 @NonNull setAuthentication(@onNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation)369 public Builder setAuthentication(@NonNull AutofillId[] ids, 370 @Nullable IntentSender authentication, @Nullable RemoteViews presentation, 371 @Nullable InlinePresentation inlinePresentation) { 372 return setAuthentication(ids, authentication, presentation, inlinePresentation, null); 373 } 374 375 /** 376 * Triggers a custom UI before before autofilling the screen with any data set in this 377 * response. 378 * 379 * <p>This method like 380 * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews, InlinePresentation)} 381 * but allows setting an {@link InlinePresentation} for the inline suggestion tooltip. 382 */ 383 @NonNull setAuthentication(@uppressLintR) @onNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation inlineTooltipPresentation)384 public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids, 385 @Nullable IntentSender authentication, @Nullable RemoteViews presentation, 386 @Nullable InlinePresentation inlinePresentation, 387 @Nullable InlinePresentation inlineTooltipPresentation) { 388 throwIfDestroyed(); 389 throwIfDisableAutofillCalled(); 390 if (mHeader != null || mFooter != null) { 391 throw new IllegalStateException("Already called #setHeader() or #setFooter()"); 392 } 393 394 if (authentication == null ^ (presentation == null && inlinePresentation == null)) { 395 throw new IllegalArgumentException("authentication and presentation " 396 + "(dropdown or inline), must be both non-null or null"); 397 } 398 mAuthentication = authentication; 399 mPresentation = presentation; 400 mInlinePresentation = inlinePresentation; 401 mInlineTooltipPresentation = inlineTooltipPresentation; 402 mAuthenticationIds = assertValid(ids); 403 return this; 404 } 405 406 /** 407 * Specifies views that should not trigger new 408 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, 409 * FillCallback)} requests. 410 * 411 * <p>This is typically used when the service cannot autofill the view; for example, a 412 * text field representing the result of a Captcha challenge. 413 */ 414 @NonNull setIgnoredIds(AutofillId...ids)415 public Builder setIgnoredIds(AutofillId...ids) { 416 throwIfDestroyed(); 417 mIgnoredIds = ids; 418 return this; 419 } 420 421 /** 422 * Adds a new {@link Dataset} to this response. 423 * 424 * <p><b>Note: </b> on Android {@link android.os.Build.VERSION_CODES#O}, the total number of 425 * datasets is limited by the Binder transaction size, so it's recommended to keep it 426 * small (in the range of 10-20 at most) and use pagination by adding a fake 427 * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated dataset} at the end 428 * with a presentation string like "Next 10" that would return a new {@link FillResponse} 429 * with the next 10 datasets, and so on. This limitation was lifted on 430 * Android {@link android.os.Build.VERSION_CODES#O_MR1}, although the Binder transaction 431 * size can still be reached if each dataset itself is too big. 432 * 433 * @return This builder. 434 */ 435 @NonNull addDataset(@ullable Dataset dataset)436 public Builder addDataset(@Nullable Dataset dataset) { 437 throwIfDestroyed(); 438 throwIfDisableAutofillCalled(); 439 if (dataset == null) { 440 return this; 441 } 442 if (mDatasets == null) { 443 mDatasets = new ArrayList<>(); 444 } 445 if (!mDatasets.add(dataset)) { 446 return this; 447 } 448 return this; 449 } 450 451 /** 452 * Sets the {@link SaveInfo} associated with this response. 453 * 454 * @return This builder. 455 */ setSaveInfo(@onNull SaveInfo saveInfo)456 public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) { 457 throwIfDestroyed(); 458 throwIfDisableAutofillCalled(); 459 mSaveInfo = saveInfo; 460 return this; 461 } 462 463 /** 464 * Sets a bundle with state that is passed to subsequent APIs that manipulate this response. 465 * 466 * <p>You can use this bundle to store intermediate state that is passed to subsequent calls 467 * to {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, 468 * FillCallback)} and {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}, and 469 * you can also retrieve it by calling {@link FillEventHistory.Event#getClientState()}. 470 * 471 * <p>If this method is called on multiple {@link FillResponse} objects for the same 472 * screen, just the latest bundle is passed back to the service. 473 * 474 * @param clientState The custom client state. 475 * @return This builder. 476 */ 477 @NonNull setClientState(@ullable Bundle clientState)478 public Builder setClientState(@Nullable Bundle clientState) { 479 throwIfDestroyed(); 480 throwIfDisableAutofillCalled(); 481 mClientState = clientState; 482 return this; 483 } 484 485 /** 486 * Sets which fields are used for 487 * <a href="AutofillService.html#FieldClassification">field classification</a> 488 * 489 * <p><b>Note:</b> This method automatically adds the 490 * {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} to the {@link #setFlags(int) flags}. 491 492 * @throws IllegalArgumentException is length of {@code ids} args is more than 493 * {@link UserData#getMaxFieldClassificationIdsSize()}. 494 * @throws IllegalStateException if {@link #build()} or {@link #disableAutofill(long)} was 495 * already called. 496 * @throws NullPointerException if {@code ids} or any element on it is {@code null}. 497 */ 498 @NonNull setFieldClassificationIds(@onNull AutofillId... ids)499 public Builder setFieldClassificationIds(@NonNull AutofillId... ids) { 500 throwIfDestroyed(); 501 throwIfDisableAutofillCalled(); 502 Preconditions.checkArrayElementsNotNull(ids, "ids"); 503 Preconditions.checkArgumentInRange(ids.length, 1, 504 UserData.getMaxFieldClassificationIdsSize(), "ids length"); 505 mFieldClassificationIds = ids; 506 mFlags |= FLAG_TRACK_CONTEXT_COMMITED; 507 return this; 508 } 509 510 /** 511 * Sets flags changing the response behavior. 512 * 513 * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and 514 * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}. 515 * 516 * @return This builder. 517 */ 518 @NonNull setFlags(@illResponseFlags int flags)519 public Builder setFlags(@FillResponseFlags int flags) { 520 throwIfDestroyed(); 521 mFlags = Preconditions.checkFlagsArgument(flags, 522 FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY); 523 return this; 524 } 525 526 /** 527 * Disables autofill for the app or activity. 528 * 529 * <p>This method is useful to optimize performance in cases where the service knows it 530 * can not autofill an app—for example, when the service has a list of "denylisted" 531 * apps such as office suites. 532 * 533 * <p>By default, it disables autofill for all activities in the app, unless the response is 534 * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}. 535 * 536 * <p>Autofill for the app or activity is automatically re-enabled after any of the 537 * following conditions: 538 * 539 * <ol> 540 * <li>{@code duration} milliseconds have passed. 541 * <li>The autofill service for the user has changed. 542 * <li>The device has rebooted. 543 * </ol> 544 * 545 * <p><b>Note:</b> Activities that are running when autofill is re-enabled remain 546 * disabled for autofill until they finish and restart. 547 * 548 * @param duration duration to disable autofill, in milliseconds. 549 * 550 * @return this builder 551 * 552 * @throws IllegalArgumentException if {@code duration} is not a positive number. 553 * @throws IllegalStateException if either {@link #addDataset(Dataset)}, 554 * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, 555 * {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or 556 * {@link #setFieldClassificationIds(AutofillId...)} was already called. 557 */ 558 @NonNull disableAutofill(long duration)559 public Builder disableAutofill(long duration) { 560 throwIfDestroyed(); 561 if (duration <= 0) { 562 throw new IllegalArgumentException("duration must be greater than 0"); 563 } 564 if (mAuthentication != null || mDatasets != null || mSaveInfo != null 565 || mFieldClassificationIds != null || mClientState != null) { 566 throw new IllegalStateException("disableAutofill() must be the only method called"); 567 } 568 569 mDisableDuration = duration; 570 return this; 571 } 572 573 /** 574 * Sets a header to be shown as the first element in the list of datasets. 575 * 576 * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset}, 577 * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this 578 * method should only be used on {@link FillResponse FillResponses} that do not require 579 * authentication (as the header could have been set directly in the main presentation in 580 * these cases). 581 * 582 * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color 583 * or background color: Autofill on different platforms may have different themes. 584 * 585 * @param header a presentation to represent the header. This presentation is not clickable 586 * —calling 587 * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would 588 * have no effect. 589 * 590 * @return this builder 591 * 592 * @throws IllegalStateException if an 593 * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was 594 * already set for this builder. 595 */ 596 // TODO(b/69796626): make it sticky / update javadoc 597 @NonNull setHeader(@onNull RemoteViews header)598 public Builder setHeader(@NonNull RemoteViews header) { 599 throwIfDestroyed(); 600 throwIfAuthenticationCalled(); 601 mHeader = Preconditions.checkNotNull(header); 602 return this; 603 } 604 605 /** 606 * Sets a footer to be shown as the last element in the list of datasets. 607 * 608 * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset}, 609 * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this 610 * method should only be used on {@link FillResponse FillResponses} that do not require 611 * authentication (as the footer could have been set directly in the main presentation in 612 * these cases). 613 * 614 * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color 615 * or background color: Autofill on different platforms may have different themes. 616 * 617 * @param footer a presentation to represent the footer. This presentation is not clickable 618 * —calling 619 * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would 620 * have no effect. 621 * 622 * @return this builder 623 * 624 * @throws IllegalStateException if the FillResponse 625 * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) 626 * requires authentication}. 627 */ 628 // TODO(b/69796626): make it sticky / update javadoc 629 @NonNull setFooter(@onNull RemoteViews footer)630 public Builder setFooter(@NonNull RemoteViews footer) { 631 throwIfDestroyed(); 632 throwIfAuthenticationCalled(); 633 mFooter = Preconditions.checkNotNull(footer); 634 return this; 635 } 636 637 /** 638 * Sets a specific {@link UserData} for field classification for this request only. 639 * 640 * <p>Any fields in this UserData will override corresponding fields in the generic 641 * UserData object 642 * 643 * @return this builder 644 * @throws IllegalStateException if the FillResponse 645 * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) 646 * requires authentication}. 647 */ 648 @NonNull setUserData(@onNull UserData userData)649 public Builder setUserData(@NonNull UserData userData) { 650 throwIfDestroyed(); 651 throwIfAuthenticationCalled(); 652 mUserData = Preconditions.checkNotNull(userData); 653 return this; 654 } 655 656 /** 657 * Sets target resource IDs of the child view in {@link RemoteViews Presentation Template} 658 * which will cancel the session when clicked. 659 * Those targets will be respectively applied to a child of the header, footer and 660 * each {@link Dataset}. 661 * 662 * @param ids array of the resource id. Empty list or non-existing id has no effect. 663 * 664 * @return this builder 665 * 666 * @throws IllegalStateException if {@link #build()} was already called. 667 */ 668 @NonNull setPresentationCancelIds(@ullable int[] ids)669 public Builder setPresentationCancelIds(@Nullable int[] ids) { 670 throwIfDestroyed(); 671 mCancelIds = ids; 672 return this; 673 } 674 675 /** 676 * Builds a new {@link FillResponse} instance. 677 * 678 * @throws IllegalStateException if any of the following conditions occur: 679 * <ol> 680 * <li>{@link #build()} was already called. 681 * <li>No call was made to {@link #addDataset(Dataset)}, 682 * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, 683 * {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)}, 684 * {@link #setClientState(Bundle)}, 685 * or {@link #setFieldClassificationIds(AutofillId...)}. 686 * <li>{@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} is called 687 * without any previous calls to {@link #addDataset(Dataset)}. 688 * </ol> 689 * 690 * @return A built response. 691 */ 692 @NonNull build()693 public FillResponse build() { 694 throwIfDestroyed(); 695 if (mAuthentication == null && mDatasets == null && mSaveInfo == null 696 && mDisableDuration == 0 && mFieldClassificationIds == null 697 && mClientState == null) { 698 throw new IllegalStateException("need to provide: at least one DataSet, or a " 699 + "SaveInfo, or an authentication with a presentation, " 700 + "or a FieldsDetection, or a client state, or disable autofill"); 701 } 702 if (mDatasets == null && (mHeader != null || mFooter != null)) { 703 throw new IllegalStateException( 704 "must add at least 1 dataset when using header or footer"); 705 } 706 707 if (mDatasets != null) { 708 for (final Dataset dataset : mDatasets) { 709 if (dataset.getFieldInlinePresentation(0) != null) { 710 mSupportsInlineSuggestions = true; 711 break; 712 } 713 } 714 } else if (mInlinePresentation != null) { 715 mSupportsInlineSuggestions = true; 716 } 717 718 mDestroyed = true; 719 return new FillResponse(this); 720 } 721 throwIfDestroyed()722 private void throwIfDestroyed() { 723 if (mDestroyed) { 724 throw new IllegalStateException("Already called #build()"); 725 } 726 } 727 throwIfDisableAutofillCalled()728 private void throwIfDisableAutofillCalled() { 729 if (mDisableDuration > 0) { 730 throw new IllegalStateException("Already called #disableAutofill()"); 731 } 732 } 733 throwIfAuthenticationCalled()734 private void throwIfAuthenticationCalled() { 735 if (mAuthentication != null) { 736 throw new IllegalStateException("Already called #setAuthentication()"); 737 } 738 } 739 } 740 741 ///////////////////////////////////// 742 // Object "contract" methods. // 743 ///////////////////////////////////// 744 @Override toString()745 public String toString() { 746 if (!sDebug) return super.toString(); 747 748 // TODO: create a dump() method instead 749 final StringBuilder builder = new StringBuilder( 750 "FillResponse : [mRequestId=" + mRequestId); 751 if (mDatasets != null) { 752 builder.append(", datasets=").append(mDatasets.getList()); 753 } 754 if (mSaveInfo != null) { 755 builder.append(", saveInfo=").append(mSaveInfo); 756 } 757 if (mClientState != null) { 758 builder.append(", hasClientState"); 759 } 760 if (mPresentation != null) { 761 builder.append(", hasPresentation"); 762 } 763 if (mInlinePresentation != null) { 764 builder.append(", hasInlinePresentation"); 765 } 766 if (mInlineTooltipPresentation != null) { 767 builder.append(", hasInlineTooltipPresentation"); 768 } 769 if (mHeader != null) { 770 builder.append(", hasHeader"); 771 } 772 if (mFooter != null) { 773 builder.append(", hasFooter"); 774 } 775 if (mAuthentication != null) { 776 builder.append(", hasAuthentication"); 777 } 778 if (mAuthenticationIds != null) { 779 builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)); 780 } 781 builder.append(", disableDuration=").append(mDisableDuration); 782 if (mFlags != 0) { 783 builder.append(", flags=").append(mFlags); 784 } 785 if (mFieldClassificationIds != null) { 786 builder.append(Arrays.toString(mFieldClassificationIds)); 787 } 788 if (mUserData != null) { 789 builder.append(", userData=").append(mUserData); 790 } 791 if (mCancelIds != null) { 792 builder.append(", mCancelIds=").append(mCancelIds.length); 793 } 794 builder.append(", mSupportInlinePresentations=").append(mSupportsInlineSuggestions); 795 return builder.append("]").toString(); 796 } 797 798 ///////////////////////////////////// 799 // Parcelable "contract" methods. // 800 ///////////////////////////////////// 801 802 @Override describeContents()803 public int describeContents() { 804 return 0; 805 } 806 807 @Override writeToParcel(Parcel parcel, int flags)808 public void writeToParcel(Parcel parcel, int flags) { 809 parcel.writeParcelable(mDatasets, flags); 810 parcel.writeParcelable(mSaveInfo, flags); 811 parcel.writeParcelable(mClientState, flags); 812 parcel.writeParcelableArray(mAuthenticationIds, flags); 813 parcel.writeParcelable(mAuthentication, flags); 814 parcel.writeParcelable(mPresentation, flags); 815 parcel.writeParcelable(mInlinePresentation, flags); 816 parcel.writeParcelable(mInlineTooltipPresentation, flags); 817 parcel.writeParcelable(mHeader, flags); 818 parcel.writeParcelable(mFooter, flags); 819 parcel.writeParcelable(mUserData, flags); 820 parcel.writeParcelableArray(mIgnoredIds, flags); 821 parcel.writeLong(mDisableDuration); 822 parcel.writeParcelableArray(mFieldClassificationIds, flags); 823 parcel.writeInt(mFlags); 824 parcel.writeIntArray(mCancelIds); 825 parcel.writeInt(mRequestId); 826 } 827 828 public static final @android.annotation.NonNull Parcelable.Creator<FillResponse> CREATOR = 829 new Parcelable.Creator<FillResponse>() { 830 @Override 831 public FillResponse createFromParcel(Parcel parcel) { 832 // Always go through the builder to ensure the data ingested by 833 // the system obeys the contract of the builder to avoid attacks 834 // using specially crafted parcels. 835 final Builder builder = new Builder(); 836 final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null); 837 final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null; 838 final int datasetCount = (datasets != null) ? datasets.size() : 0; 839 for (int i = 0; i < datasetCount; i++) { 840 builder.addDataset(datasets.get(i)); 841 } 842 builder.setSaveInfo(parcel.readParcelable(null)); 843 builder.setClientState(parcel.readParcelable(null)); 844 845 // Sets authentication state. 846 final AutofillId[] authenticationIds = parcel.readParcelableArray(null, 847 AutofillId.class); 848 final IntentSender authentication = parcel.readParcelable(null); 849 final RemoteViews presentation = parcel.readParcelable(null); 850 final InlinePresentation inlinePresentation = parcel.readParcelable(null); 851 final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null); 852 if (authenticationIds != null) { 853 builder.setAuthentication(authenticationIds, authentication, presentation, 854 inlinePresentation, inlineTooltipPresentation); 855 } 856 final RemoteViews header = parcel.readParcelable(null); 857 if (header != null) { 858 builder.setHeader(header); 859 } 860 final RemoteViews footer = parcel.readParcelable(null); 861 if (footer != null) { 862 builder.setFooter(footer); 863 } 864 final UserData userData = parcel.readParcelable(null); 865 if (userData != null) { 866 builder.setUserData(userData); 867 } 868 869 builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class)); 870 final long disableDuration = parcel.readLong(); 871 if (disableDuration > 0) { 872 builder.disableAutofill(disableDuration); 873 } 874 final AutofillId[] fieldClassifactionIds = 875 parcel.readParcelableArray(null, AutofillId.class); 876 if (fieldClassifactionIds != null) { 877 builder.setFieldClassificationIds(fieldClassifactionIds); 878 } 879 builder.setFlags(parcel.readInt()); 880 final int[] cancelIds = parcel.createIntArray(); 881 builder.setPresentationCancelIds(cancelIds); 882 883 final FillResponse response = builder.build(); 884 response.setRequestId(parcel.readInt()); 885 886 return response; 887 } 888 889 @Override 890 public FillResponse[] newArray(int size) { 891 return new FillResponse[size]; 892 } 893 }; 894 } 895