1 /* 2 * Copyright (C) 2017 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.Flags.FLAG_AUTOFILL_W_METRICS; 20 import static android.view.autofill.Helper.sVerbose; 21 22 import android.annotation.FlaggedApi; 23 import android.annotation.IntDef; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.content.IntentSender; 27 import android.os.Bundle; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.util.ArrayMap; 31 import android.util.ArraySet; 32 import android.util.Log; 33 import android.view.autofill.AutofillId; 34 import android.view.autofill.AutofillManager; 35 36 import com.android.internal.util.ArrayUtils; 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.Collections; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.Set; 47 48 /** 49 * Describes what happened after the last 50 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} 51 * call. 52 * 53 * <p>This history is typically used to keep track of previous user actions to optimize further 54 * requests. For example, the service might return email addresses in alphabetical order by 55 * default, but change that order based on the address the user picked on previous requests. 56 * 57 * <p>The history is not persisted over reboots, and it's cleared every time the service 58 * replies to a 59 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} 60 * by calling {@link FillCallback#onSuccess(FillResponse)} or 61 * {@link FillCallback#onFailure(CharSequence)} (if the service doesn't call any of these methods, 62 * the history will clear out after some pre-defined time). 63 */ 64 public final class FillEventHistory implements Parcelable { 65 private static final String TAG = "FillEventHistory"; 66 67 /** 68 * The ID of the autofill session that created the {@link FillResponse}. 69 * 70 * TODO: add this to the parcel. 71 */ 72 private final int mSessionId; 73 74 @Nullable private final Bundle mClientState; 75 @Nullable List<Event> mEvents; 76 77 /** 78 * Returns the unique identifier of this FillEventHistory. 79 * 80 * <p>This is used to differentiate individual FillEventHistory. 81 */ 82 @FlaggedApi(FLAG_AUTOFILL_W_METRICS) getSessionId()83 public int getSessionId() { 84 return mSessionId; 85 } 86 87 /** 88 * Returns the client state set in the previous {@link FillResponse}. 89 * 90 * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous 91 * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} 92 * , which is not necessary the same app being autofilled now. 93 * 94 * @deprecated use {@link #getEvents()} then {@link Event#getClientState()} instead. 95 */ 96 @Deprecated getClientState()97 @Nullable public Bundle getClientState() { 98 return mClientState; 99 } 100 101 /** 102 * Returns the events occurred after the latest call to 103 * {@link FillCallback#onSuccess(FillResponse)}. 104 * 105 * @return The list of events or {@code null} if non occurred. 106 */ getEvents()107 @Nullable public List<Event> getEvents() { 108 return mEvents; 109 } 110 111 /** 112 * @hide 113 */ addEvent(Event event)114 public void addEvent(Event event) { 115 if (mEvents == null) { 116 mEvents = new ArrayList<>(1); 117 } 118 mEvents.add(event); 119 } 120 121 /** 122 * @hide 123 */ FillEventHistory(int sessionId, @Nullable Bundle clientState)124 public FillEventHistory(int sessionId, @Nullable Bundle clientState) { 125 mClientState = clientState; 126 mSessionId = sessionId; 127 } 128 129 @Override toString()130 public String toString() { 131 return mEvents == null ? "no events" : mEvents.toString(); 132 } 133 134 @Override describeContents()135 public int describeContents() { 136 return 0; 137 } 138 139 @Override writeToParcel(Parcel parcel, int flags)140 public void writeToParcel(Parcel parcel, int flags) { 141 parcel.writeBundle(mClientState); 142 if (mEvents == null) { 143 parcel.writeInt(0); 144 } else { 145 parcel.writeInt(mEvents.size()); 146 147 int numEvents = mEvents.size(); 148 for (int i = 0; i < numEvents; i++) { 149 Event event = mEvents.get(i); 150 parcel.writeInt(event.mEventType); 151 parcel.writeString(event.mDatasetId); 152 parcel.writeBundle(event.mClientState); 153 parcel.writeStringList(event.mSelectedDatasetIds); 154 parcel.writeArraySet(event.mIgnoredDatasetIds); 155 parcel.writeTypedList(event.mChangedFieldIds); 156 parcel.writeStringList(event.mChangedDatasetIds); 157 158 parcel.writeTypedList(event.mManuallyFilledFieldIds); 159 if (event.mManuallyFilledFieldIds != null) { 160 final int size = event.mManuallyFilledFieldIds.size(); 161 for (int j = 0; j < size; j++) { 162 parcel.writeStringList(event.mManuallyFilledDatasetIds.get(j)); 163 } 164 } 165 final AutofillId[] detectedFields = event.mDetectedFieldIds; 166 parcel.writeParcelableArray(detectedFields, flags); 167 if (detectedFields != null) { 168 FieldClassification.writeArrayToParcel(parcel, 169 event.mDetectedFieldClassifications); 170 } 171 parcel.writeInt(event.mSaveDialogNotShowReason); 172 parcel.writeInt(event.mUiType); 173 if (Flags.addLastFocusedIdToFillEventHistory()) { 174 parcel.writeParcelable(event.mFocusedId, 0); 175 } 176 } 177 } 178 } 179 180 /** 181 * Description of an event that occurred after the latest call to 182 * {@link FillCallback#onSuccess(FillResponse)}. 183 */ 184 public static final class Event { 185 /** 186 * A dataset was selected. The dataset selected can be read from {@link #getDatasetId()}. 187 * 188 * <p><b>Note: </b>on Android {@link android.os.Build.VERSION_CODES#O}, this event was also 189 * incorrectly reported after a 190 * {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was 191 * selected and the service returned a dataset in the 192 * {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT} of the activity launched from that 193 * {@link IntentSender}. This behavior was fixed on Android 194 * {@link android.os.Build.VERSION_CODES#O_MR1}. 195 */ 196 public static final int TYPE_DATASET_SELECTED = 0; 197 198 /** 199 * A {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was 200 * selected. The dataset authenticated can be read from {@link #getDatasetId()}. 201 */ 202 public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; 203 204 /** 205 * A {@link FillResponse.Builder#setAuthentication(android.view.autofill.AutofillId[], 206 * IntentSender, android.widget.RemoteViews) fill response authentication} was selected. 207 */ 208 public static final int TYPE_AUTHENTICATION_SELECTED = 2; 209 210 /** A save UI was shown. */ 211 public static final int TYPE_SAVE_SHOWN = 3; 212 213 /** 214 * A committed autofill context for which the autofill service provided datasets. 215 * 216 * <p>This event is useful to track: 217 * <ul> 218 * <li>Which datasets (if any) were selected by the user 219 * ({@link #getSelectedDatasetIds()}). 220 * <li>Which datasets (if any) were NOT selected by the user 221 * ({@link #getIgnoredDatasetIds()}). 222 * <li>Which fields in the selected datasets were changed by the user after the dataset 223 * was selected ({@link #getChangedFields()}. 224 * <li>Which fields match the {@link UserData} set by the service. 225 * </ul> 226 * 227 * <p><b>Note: </b>This event is only generated when: 228 * <ul> 229 * <li>The autofill context is committed. 230 * <li>The service provides at least one dataset in the 231 * {@link FillResponse fill responses} associated with the context. 232 * <li>The last {@link FillResponse fill responses} associated with the context has the 233 * {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} flag. 234 * </ul> 235 * 236 * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill 237 * contexts. 238 */ 239 public static final int TYPE_CONTEXT_COMMITTED = 4; 240 241 /** 242 * A dataset selector was shown. 243 * 244 * <p>This event is fired whenever the autofill UI was presented to the user.</p> 245 */ 246 public static final int TYPE_DATASETS_SHOWN = 5; 247 248 /** 249 * The app/user requested for a field to be Autofilled. 250 * 251 * This event is fired when the view has been entered (by user or app) in order 252 * to differentiate from FillRequests that have been pretriggered for FillDialogs. 253 * 254 * For example, the user might navigate away from a screen without tapping any 255 * fields. In this case, a FillRequest/FillResponse has been generated, but was 256 * not used for Autofilling. The user did not intend to see an Autofill result, 257 * but a FillRequest was still generated. This is different from when the user 258 * did tap on a field after the pretriggered FillRequest, this event will appear 259 * in the FillEventHistory, signaling that the user did intend to Autofill 260 * something. 261 */ 262 public static final int TYPE_VIEW_REQUESTED_AUTOFILL = 6; 263 264 /** @hide */ 265 @IntDef(prefix = { "TYPE_" }, value = { 266 TYPE_DATASET_SELECTED, 267 TYPE_DATASET_AUTHENTICATION_SELECTED, 268 TYPE_AUTHENTICATION_SELECTED, 269 TYPE_SAVE_SHOWN, 270 TYPE_CONTEXT_COMMITTED, 271 TYPE_DATASETS_SHOWN, 272 TYPE_VIEW_REQUESTED_AUTOFILL 273 }) 274 @Retention(RetentionPolicy.SOURCE) 275 @interface EventIds{} 276 277 /** No reason for save dialog. */ 278 public static final int NO_SAVE_UI_REASON_NONE = 0; 279 280 /** The SaveInfo associated with the FillResponse is null. */ 281 public static final int NO_SAVE_UI_REASON_NO_SAVE_INFO = 1; 282 283 /** The service asked to delay save. */ 284 public static final int NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG = 2; 285 286 /** There was empty value for required ids. */ 287 public static final int NO_SAVE_UI_REASON_HAS_EMPTY_REQUIRED = 3; 288 289 /** No value has been changed. */ 290 public static final int NO_SAVE_UI_REASON_NO_VALUE_CHANGED = 4; 291 292 /** Fields failed validation. */ 293 public static final int NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED = 5; 294 295 /** All fields matched contents of datasets. */ 296 public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6; 297 298 /** 299 * Credential Manager is invoked instead of Autofill. When that happens, Save Dialog cannot 300 * be shown, and this will be populated in 301 */ 302 @FlaggedApi(FLAG_AUTOFILL_W_METRICS) 303 public static final int NO_SAVE_UI_REASON_USING_CREDMAN = 7; 304 305 /** @hide */ 306 @IntDef(prefix = { "NO_SAVE_UI_REASON_" }, value = { 307 NO_SAVE_UI_REASON_NONE, 308 NO_SAVE_UI_REASON_NO_SAVE_INFO, 309 NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG, 310 NO_SAVE_UI_REASON_HAS_EMPTY_REQUIRED, 311 NO_SAVE_UI_REASON_NO_VALUE_CHANGED, 312 NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED, 313 NO_SAVE_UI_REASON_DATASET_MATCH 314 }) 315 @Retention(RetentionPolicy.SOURCE) 316 public @interface NoSaveReason{} 317 318 /** The autofill suggestion presentation is unknown, this will be set for the event 319 * that is unrelated to fill Ui presentation */ 320 public static final int UI_TYPE_UNKNOWN = 0; 321 322 /** The autofill suggestion is shown as a menu popup presentation. */ 323 public static final int UI_TYPE_MENU = 1; 324 325 /** The autofill suggestion is shown as a keyboard inline presentation. */ 326 public static final int UI_TYPE_INLINE = 2; 327 328 /** The autofill suggestion is shown as a dialog presentation. */ 329 public static final int UI_TYPE_DIALOG = 3; 330 331 /** 332 * The autofill suggestion is shown os a credman bottom sheet 333 * 334 * <p>Note, this was introduced as bottom sheet even though it applies to all credman UI 335 * types. Instead of exposing this directly to the public, the generic UI_TYPE_CREDMAN is 336 * introduced with the same number. 337 * 338 * @hide 339 */ 340 public static final int UI_TYPE_CREDMAN_BOTTOM_SHEET = 4; 341 342 /** Credential Manager suggestions are shown instead of Autofill suggestion */ 343 @FlaggedApi(FLAG_AUTOFILL_W_METRICS) 344 public static final int UI_TYPE_CREDENTIAL_MANAGER = 4; 345 346 /** @hide */ 347 @IntDef(prefix = { "UI_TYPE_" }, value = { 348 UI_TYPE_UNKNOWN, 349 UI_TYPE_MENU, 350 UI_TYPE_INLINE, 351 UI_TYPE_DIALOG, 352 UI_TYPE_CREDMAN_BOTTOM_SHEET 353 }) 354 @Retention(RetentionPolicy.SOURCE) 355 public @interface UiType {} 356 357 @EventIds private final int mEventType; 358 @Nullable private final String mDatasetId; 359 @Nullable private final Bundle mClientState; 360 361 // Note: mSelectedDatasetIds is stored as List<> instead of Set because Session already 362 // stores it as List 363 @Nullable private final List<String> mSelectedDatasetIds; 364 @Nullable private final ArraySet<String> mIgnoredDatasetIds; 365 366 @Nullable private final ArrayList<AutofillId> mChangedFieldIds; 367 @Nullable private final ArrayList<String> mChangedDatasetIds; 368 369 @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds; 370 @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds; 371 372 @Nullable private final AutofillId[] mDetectedFieldIds; 373 @Nullable private final FieldClassification[] mDetectedFieldClassifications; 374 375 @NoSaveReason private final int mSaveDialogNotShowReason; 376 377 378 @UiType 379 private final int mUiType; 380 381 @Nullable private final AutofillId mFocusedId; 382 383 /** 384 * Returns the type of the event. 385 * 386 * @return The type of the event 387 */ getType()388 public int getType() { 389 return mEventType; 390 } 391 392 /** Gets the {@code AutofillId} that's focused at the time of action */ 393 @FlaggedApi(FLAG_AUTOFILL_W_METRICS) 394 @Nullable getFocusedId()395 public AutofillId getFocusedId() { 396 return mFocusedId; 397 } 398 399 /** 400 * Returns the id of dataset the id was on. 401 * 402 * @return The id of dataset, or {@code null} the event is not associated with a dataset. 403 */ getDatasetId()404 @Nullable public String getDatasetId() { 405 return mDatasetId; 406 } 407 408 /** 409 * Returns the client state from the {@link FillResponse} used to generate this event. 410 * 411 * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous 412 * {@link 413 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}, 414 * which is not necessary the same app being autofilled now. 415 */ getClientState()416 @Nullable public Bundle getClientState() { 417 return mClientState; 418 } 419 420 /** 421 * Returns which datasets were selected by the user. 422 * 423 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 424 */ getSelectedDatasetIds()425 @NonNull public Set<String> getSelectedDatasetIds() { 426 return mSelectedDatasetIds == null ? Collections.emptySet() 427 : new ArraySet<>(mSelectedDatasetIds); 428 } 429 430 /** 431 * Returns which datasets were shown to the user. 432 * 433 * <p><b>Note: </b>Only set on events of type {@link #TYPE_DATASETS_SHOWN}. 434 */ 435 @FlaggedApi(FLAG_AUTOFILL_W_METRICS) 436 @NonNull getShownDatasetIds()437 public Set<String> getShownDatasetIds() { 438 return Collections.emptySet(); 439 } 440 441 /** 442 * Returns which datasets were NOT selected by the user. 443 * 444 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 445 */ getIgnoredDatasetIds()446 @NonNull public Set<String> getIgnoredDatasetIds() { 447 return mIgnoredDatasetIds == null ? Collections.emptySet() : mIgnoredDatasetIds; 448 } 449 450 /** 451 * Returns which fields in the selected datasets were changed by the user after the dataset 452 * was selected. 453 * 454 * <p>For example, server provides: 455 * 456 * <pre class="prettyprint"> 457 * FillResponse response = new FillResponse.Builder() 458 * .addDataset(new Dataset.Builder(presentation1) 459 * .setId("4815") 460 * .setValue(usernameId, AutofillValue.forText("MrPlow")) 461 * .build()) 462 * .addDataset(new Dataset.Builder(presentation2) 463 * .setId("162342") 464 * .setValue(passwordId, AutofillValue.forText("D'OH")) 465 * .build()) 466 * .build(); 467 * </pre> 468 * 469 * <p>User select both datasets (for username and password) but after the fields are 470 * autofilled, user changes them to: 471 * 472 * <pre class="prettyprint"> 473 * username = "ElBarto"; 474 * password = "AyCaramba"; 475 * </pre> 476 * 477 * <p>Then the result is the following map: 478 * 479 * <pre class="prettyprint"> 480 * usernameId => "4815" 481 * passwordId => "162342" 482 * </pre> 483 * 484 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 485 * 486 * @return map map whose key is the id of the change fields, and value is the id of 487 * dataset that has that field and was selected by the user. 488 */ getChangedFields()489 @NonNull public Map<AutofillId, String> getChangedFields() { 490 if (mChangedFieldIds == null || mChangedDatasetIds == null) { 491 return Collections.emptyMap(); 492 } 493 494 final int size = mChangedFieldIds.size(); 495 final ArrayMap<AutofillId, String> changedFields = new ArrayMap<>(size); 496 for (int i = 0; i < size; i++) { 497 changedFields.put(mChangedFieldIds.get(i), mChangedDatasetIds.get(i)); 498 } 499 return changedFields; 500 } 501 502 /** 503 * Gets the <a href="AutofillService.html#FieldClassification">field classification</a> 504 * results. 505 * 506 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the 507 * service requested {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...) 508 * field classification}. 509 */ getFieldsClassification()510 @NonNull public Map<AutofillId, FieldClassification> getFieldsClassification() { 511 if (mDetectedFieldIds == null) { 512 return Collections.emptyMap(); 513 } 514 final int size = mDetectedFieldIds.length; 515 final ArrayMap<AutofillId, FieldClassification> map = new ArrayMap<>(size); 516 for (int i = 0; i < size; i++) { 517 final AutofillId id = mDetectedFieldIds[i]; 518 final FieldClassification fc = mDetectedFieldClassifications[i]; 519 if (sVerbose) { 520 Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", fc=" + fc); 521 } 522 map.put(id, fc); 523 } 524 return map; 525 } 526 527 /** 528 * Returns which fields were available on datasets provided by the service but manually 529 * entered by the user. 530 * 531 * <p>For example, server provides: 532 * 533 * <pre class="prettyprint"> 534 * FillResponse response = new FillResponse.Builder() 535 * .addDataset(new Dataset.Builder(presentation1) 536 * .setId("4815") 537 * .setValue(usernameId, AutofillValue.forText("MrPlow")) 538 * .setValue(passwordId, AutofillValue.forText("AyCaramba")) 539 * .build()) 540 * .addDataset(new Dataset.Builder(presentation2) 541 * .setId("162342") 542 * .setValue(usernameId, AutofillValue.forText("ElBarto")) 543 * .setValue(passwordId, AutofillValue.forText("D'OH")) 544 * .build()) 545 * .addDataset(new Dataset.Builder(presentation3) 546 * .setId("108") 547 * .setValue(usernameId, AutofillValue.forText("MrPlow")) 548 * .setValue(passwordId, AutofillValue.forText("D'OH")) 549 * .build()) 550 * .build(); 551 * </pre> 552 * 553 * <p>User doesn't select a dataset but manually enters: 554 * 555 * <pre class="prettyprint"> 556 * username = "MrPlow"; 557 * password = "D'OH"; 558 * </pre> 559 * 560 * <p>Then the result is the following map: 561 * 562 * <pre class="prettyprint"> 563 * usernameId => { "4815", "108"} 564 * passwordId => { "162342", "108" } 565 * </pre> 566 * 567 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 568 * 569 * @return map map whose key is the id of the manually-entered field, and value is the 570 * ids of the datasets that have that value but were not selected by the user. 571 */ getManuallyEnteredField()572 @NonNull public Map<AutofillId, Set<String>> getManuallyEnteredField() { 573 if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) { 574 return Collections.emptyMap(); 575 } 576 577 final int size = mManuallyFilledFieldIds.size(); 578 final Map<AutofillId, Set<String>> manuallyFilledFields = new ArrayMap<>(size); 579 for (int i = 0; i < size; i++) { 580 final AutofillId fieldId = mManuallyFilledFieldIds.get(i); 581 final ArrayList<String> datasetIds = mManuallyFilledDatasetIds.get(i); 582 manuallyFilledFields.put(fieldId, new ArraySet<>(datasetIds)); 583 } 584 return manuallyFilledFields; 585 } 586 587 /** 588 * Returns the reason why a save dialog was not shown. 589 * 590 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. For the other 591 * event types, the reason is set to NO_SAVE_UI_REASON_NONE. 592 * 593 * @return The reason why a save dialog was not shown. 594 */ 595 @NoSaveReason getNoSaveUiReason()596 public int getNoSaveUiReason() { 597 return mSaveDialogNotShowReason; 598 } 599 600 /** 601 * Returns fill suggestion ui presentation type which corresponds to types 602 * defined in {@link android.service.autofill.Presentations). 603 * 604 * <p><b>Note: </b>Only set on events of type {@link #TYPE_DATASETS_SHOWN} and 605 * {@link #TYPE_DATASET_SELECTED}. For the other event types, the type is set to 606 * {@link #UI_TYPE_UNKNOWN }. 607 * 608 * @return The ui presentation type shown for user. 609 */ 610 @UiType getUiType()611 public int getUiType() { 612 return mUiType; 613 } 614 615 /** 616 * Creates a new event. 617 * 618 * @param eventType The type of the event 619 * @param datasetId The dataset the event was on, or {@code null} if the event was on the 620 * whole response. 621 * @param clientState The client state associated with the event. 622 * @param selectedDatasetIds The ids of datasets selected by the user. 623 * @param ignoredDatasetIds The ids of datasets NOT select by the user. 624 * @param changedFieldIds The ids of fields changed by the user. 625 * @param changedDatasetIds The ids of the datasets that havd values matching the 626 * respective entry on {@code changedFieldIds}. 627 * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user 628 * and belonged to datasets. 629 * @param manuallyFilledDatasetIds The ids of datasets that had values matching the 630 * respective entry on {@code manuallyFilledFieldIds}. 631 * @param detectedFieldClassifications the field classification matches. 632 * @param focusedId the field which was focused at the time of event trigger 633 * 634 * @throws IllegalArgumentException If the length of {@code changedFieldIds} and 635 * {@code changedDatasetIds} doesn't match. 636 * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and 637 * {@code manuallyFilledDatasetIds} doesn't match. 638 * 639 * @hide 640 */ Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, @Nullable List<String> selectedDatasetIds, @Nullable ArraySet<String> ignoredDatasetIds, @Nullable ArrayList<AutofillId> changedFieldIds, @Nullable ArrayList<String> changedDatasetIds, @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, @Nullable AutofillId[] detectedFieldIds, @Nullable FieldClassification[] detectedFieldClassifications, @Nullable AutofillId focusedId)641 public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, 642 @Nullable List<String> selectedDatasetIds, 643 @Nullable ArraySet<String> ignoredDatasetIds, 644 @Nullable ArrayList<AutofillId> changedFieldIds, 645 @Nullable ArrayList<String> changedDatasetIds, 646 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, 647 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, 648 @Nullable AutofillId[] detectedFieldIds, 649 @Nullable FieldClassification[] detectedFieldClassifications, 650 @Nullable AutofillId focusedId) { 651 this(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasetIds, 652 changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, 653 manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications, 654 NO_SAVE_UI_REASON_NONE, focusedId); 655 } 656 657 /** 658 * Creates a new event. 659 * 660 * @param eventType The type of the event 661 * @param datasetId The dataset the event was on, or {@code null} if the event was on the 662 * whole response. 663 * @param clientState The client state associated with the event. 664 * @param selectedDatasetIds The ids of datasets selected by the user. 665 * @param ignoredDatasetIds The ids of datasets NOT select by the user. 666 * @param changedFieldIds The ids of fields changed by the user. 667 * @param changedDatasetIds The ids of the datasets that havd values matching the 668 * respective entry on {@code changedFieldIds}. 669 * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user 670 * and belonged to datasets. 671 * @param manuallyFilledDatasetIds The ids of datasets that had values matching the 672 * respective entry on {@code manuallyFilledFieldIds}. 673 * @param detectedFieldClassifications the field classification matches. 674 * @param saveDialogNotShowReason The reason why a save dialog was not shown. 675 * @param focusedId the field which was focused at the time of event trigger 676 * 677 * @throws IllegalArgumentException If the length of {@code changedFieldIds} and 678 * {@code changedDatasetIds} doesn't match. 679 * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and 680 * {@code manuallyFilledDatasetIds} doesn't match. 681 * 682 * @hide 683 */ Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, @Nullable List<String> selectedDatasetIds, @Nullable ArraySet<String> ignoredDatasetIds, @Nullable ArrayList<AutofillId> changedFieldIds, @Nullable ArrayList<String> changedDatasetIds, @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, @Nullable AutofillId[] detectedFieldIds, @Nullable FieldClassification[] detectedFieldClassifications, int saveDialogNotShowReason, @Nullable AutofillId focusedId)684 public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, 685 @Nullable List<String> selectedDatasetIds, 686 @Nullable ArraySet<String> ignoredDatasetIds, 687 @Nullable ArrayList<AutofillId> changedFieldIds, 688 @Nullable ArrayList<String> changedDatasetIds, 689 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, 690 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, 691 @Nullable AutofillId[] detectedFieldIds, 692 @Nullable FieldClassification[] detectedFieldClassifications, 693 int saveDialogNotShowReason, 694 @Nullable AutofillId focusedId) { 695 this(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasetIds, 696 changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, 697 manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications, 698 saveDialogNotShowReason, UI_TYPE_UNKNOWN, focusedId); 699 } 700 701 /** 702 * Creates a new event. 703 * 704 * @param eventType The type of the event 705 * @param datasetId The dataset the event was on, or {@code null} if the event was on the 706 * whole response. 707 * @param clientState The client state associated with the event. 708 * @param selectedDatasetIds The ids of datasets selected by the user. 709 * @param ignoredDatasetIds The ids of datasets NOT select by the user. 710 * @param changedFieldIds The ids of fields changed by the user. 711 * @param changedDatasetIds The ids of the datasets that havd values matching the 712 * respective entry on {@code changedFieldIds}. 713 * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user 714 * and belonged to datasets. 715 * @param manuallyFilledDatasetIds The ids of datasets that had values matching the 716 * respective entry on {@code manuallyFilledFieldIds}. 717 * @param detectedFieldClassifications the field classification matches. 718 * @param saveDialogNotShowReason The reason why a save dialog was not shown. 719 * @param uiType The ui presentation type for fill suggestion. 720 * @param focusedId the field which was focused at the time of event trigger 721 * 722 * @throws IllegalArgumentException If the length of {@code changedFieldIds} and 723 * {@code changedDatasetIds} doesn't match. 724 * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and 725 * {@code manuallyFilledDatasetIds} doesn't match. 726 * 727 * @hide 728 */ Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, @Nullable List<String> selectedDatasetIds, @Nullable ArraySet<String> ignoredDatasetIds, @Nullable ArrayList<AutofillId> changedFieldIds, @Nullable ArrayList<String> changedDatasetIds, @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, @Nullable AutofillId[] detectedFieldIds, @Nullable FieldClassification[] detectedFieldClassifications, int saveDialogNotShowReason, int uiType, @Nullable AutofillId focusedId)729 public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, 730 @Nullable List<String> selectedDatasetIds, 731 @Nullable ArraySet<String> ignoredDatasetIds, 732 @Nullable ArrayList<AutofillId> changedFieldIds, 733 @Nullable ArrayList<String> changedDatasetIds, 734 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, 735 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, 736 @Nullable AutofillId[] detectedFieldIds, 737 @Nullable FieldClassification[] detectedFieldClassifications, 738 int saveDialogNotShowReason, int uiType, @Nullable AutofillId focusedId) { 739 mEventType = Preconditions.checkArgumentInRange(eventType, 0, 740 TYPE_VIEW_REQUESTED_AUTOFILL, "eventType"); 741 mDatasetId = datasetId; 742 mClientState = clientState; 743 mSelectedDatasetIds = selectedDatasetIds; 744 mIgnoredDatasetIds = ignoredDatasetIds; 745 if (changedFieldIds != null) { 746 Preconditions.checkArgument(!ArrayUtils.isEmpty(changedFieldIds) 747 && changedDatasetIds != null 748 && changedFieldIds.size() == changedDatasetIds.size(), 749 "changed ids must have same length and not be empty"); 750 } 751 mChangedFieldIds = changedFieldIds; 752 mChangedDatasetIds = changedDatasetIds; 753 if (manuallyFilledFieldIds != null) { 754 Preconditions.checkArgument(!ArrayUtils.isEmpty(manuallyFilledFieldIds) 755 && manuallyFilledDatasetIds != null 756 && manuallyFilledFieldIds.size() == manuallyFilledDatasetIds.size(), 757 "manually filled ids must have same length and not be empty"); 758 } 759 mManuallyFilledFieldIds = manuallyFilledFieldIds; 760 mManuallyFilledDatasetIds = manuallyFilledDatasetIds; 761 762 mDetectedFieldIds = detectedFieldIds; 763 mDetectedFieldClassifications = detectedFieldClassifications; 764 765 mSaveDialogNotShowReason = Preconditions.checkArgumentInRange(saveDialogNotShowReason, 766 NO_SAVE_UI_REASON_NONE, NO_SAVE_UI_REASON_DATASET_MATCH, 767 "saveDialogNotShowReason"); 768 mUiType = uiType; 769 mFocusedId = focusedId; 770 } 771 772 @Override toString()773 public String toString() { 774 return "FillEvent [datasetId=" + mDatasetId 775 + ", type=" + eventToString(mEventType) 776 + ", uiType=" + uiTypeToString(mUiType) 777 + ", selectedDatasets=" + mSelectedDatasetIds 778 + ", ignoredDatasetIds=" + mIgnoredDatasetIds 779 + ", changedFieldIds=" + mChangedFieldIds 780 + ", changedDatasetsIds=" + mChangedDatasetIds 781 + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds 782 + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds 783 + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds) 784 + ", detectedFieldClassifications =" 785 + Arrays.toString(mDetectedFieldClassifications) 786 + ", saveDialogNotShowReason=" + mSaveDialogNotShowReason 787 + "]"; 788 } 789 eventToString(int eventType)790 private static String eventToString(int eventType) { 791 switch (eventType) { 792 case TYPE_DATASET_SELECTED: 793 return "TYPE_DATASET_SELECTED"; 794 case TYPE_DATASET_AUTHENTICATION_SELECTED: 795 return "TYPE_DATASET_AUTHENTICATION_SELECTED"; 796 case TYPE_AUTHENTICATION_SELECTED: 797 return "TYPE_AUTHENTICATION_SELECTED"; 798 case TYPE_SAVE_SHOWN: 799 return "TYPE_SAVE_SHOWN"; 800 case TYPE_CONTEXT_COMMITTED: 801 return "TYPE_CONTEXT_COMMITTED"; 802 case TYPE_DATASETS_SHOWN: 803 return "TYPE_DATASETS_SHOWN"; 804 case TYPE_VIEW_REQUESTED_AUTOFILL: 805 return "TYPE_VIEW_REQUESTED_AUTOFILL"; 806 default: 807 return "TYPE_UNKNOWN"; 808 } 809 } 810 uiTypeToString(int uiType)811 private static String uiTypeToString(int uiType) { 812 switch (uiType) { 813 case UI_TYPE_MENU: 814 return "UI_TYPE_MENU"; 815 case UI_TYPE_INLINE: 816 return "UI_TYPE_INLINE"; 817 case UI_TYPE_DIALOG: 818 return "UI_TYPE_FILL_DIALOG"; 819 case UI_TYPE_CREDMAN_BOTTOM_SHEET: 820 return "UI_TYPE_CREDMAN_BOTTOM_SHEET"; 821 default: 822 return "UI_TYPE_UNKNOWN"; 823 } 824 } 825 } 826 827 public static final @android.annotation.NonNull Parcelable.Creator<FillEventHistory> CREATOR = 828 new Parcelable.Creator<FillEventHistory>() { 829 @Override 830 public FillEventHistory createFromParcel(Parcel parcel) { 831 FillEventHistory selection = new FillEventHistory(0, parcel.readBundle()); 832 833 final int numEvents = parcel.readInt(); 834 for (int i = 0; i < numEvents; i++) { 835 final int eventType = parcel.readInt(); 836 final String datasetId = parcel.readString(); 837 final Bundle clientState = parcel.readBundle(); 838 final ArrayList<String> selectedDatasetIds = parcel.createStringArrayList(); 839 @SuppressWarnings("unchecked") 840 final ArraySet<String> ignoredDatasets = 841 (ArraySet<String>) parcel.readArraySet(null); 842 final ArrayList<AutofillId> changedFieldIds = 843 parcel.createTypedArrayList(AutofillId.CREATOR); 844 final ArrayList<String> changedDatasetIds = parcel.createStringArrayList(); 845 846 final ArrayList<AutofillId> manuallyFilledFieldIds = 847 parcel.createTypedArrayList(AutofillId.CREATOR); 848 final ArrayList<ArrayList<String>> manuallyFilledDatasetIds; 849 if (manuallyFilledFieldIds != null) { 850 final int size = manuallyFilledFieldIds.size(); 851 manuallyFilledDatasetIds = new ArrayList<>(size); 852 for (int j = 0; j < size; j++) { 853 manuallyFilledDatasetIds.add(parcel.createStringArrayList()); 854 } 855 } else { 856 manuallyFilledDatasetIds = null; 857 } 858 final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null, 859 AutofillId.class); 860 final FieldClassification[] detectedFieldClassifications = 861 (detectedFieldIds != null) 862 ? FieldClassification.readArrayFromParcel(parcel) 863 : null; 864 final int saveDialogNotShowReason = parcel.readInt(); 865 final int uiType = parcel.readInt(); 866 AutofillId focusedId = null; 867 if (Flags.addLastFocusedIdToFillEventHistory()) { 868 focusedId = parcel.readParcelable(null, AutofillId.class); 869 } 870 871 selection.addEvent(new Event(eventType, datasetId, clientState, 872 selectedDatasetIds, ignoredDatasets, 873 changedFieldIds, changedDatasetIds, 874 manuallyFilledFieldIds, manuallyFilledDatasetIds, 875 detectedFieldIds, detectedFieldClassifications, 876 saveDialogNotShowReason, uiType, focusedId)); 877 } 878 return selection; 879 } 880 881 @Override 882 public FillEventHistory[] newArray(int size) { 883 return new FillEventHistory[size]; 884 } 885 }; 886 } 887