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