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