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 /** @hide */ 237 @IntDef(prefix = { "TYPE_" }, value = { 238 TYPE_DATASET_SELECTED, 239 TYPE_DATASET_AUTHENTICATION_SELECTED, 240 TYPE_AUTHENTICATION_SELECTED, 241 TYPE_SAVE_SHOWN, 242 TYPE_CONTEXT_COMMITTED, 243 TYPE_DATASETS_SHOWN 244 }) 245 @Retention(RetentionPolicy.SOURCE) 246 @interface EventIds{} 247 248 /** No reason for save dialog. */ 249 public static final int NO_SAVE_UI_REASON_NONE = 0; 250 251 /** The SaveInfo associated with the FillResponse is null. */ 252 public static final int NO_SAVE_UI_REASON_NO_SAVE_INFO = 1; 253 254 /** The service asked to delay save. */ 255 public static final int NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG = 2; 256 257 /** There was empty value for required ids. */ 258 public static final int NO_SAVE_UI_REASON_HAS_EMPTY_REQUIRED = 3; 259 260 /** No value has been changed. */ 261 public static final int NO_SAVE_UI_REASON_NO_VALUE_CHANGED = 4; 262 263 /** Fields failed validation. */ 264 public static final int NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED = 5; 265 266 /** All fields matched contents of datasets. */ 267 public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6; 268 269 /** @hide */ 270 @IntDef(prefix = { "NO_SAVE_UI_REASON_" }, value = { 271 NO_SAVE_UI_REASON_NONE, 272 NO_SAVE_UI_REASON_NO_SAVE_INFO, 273 NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG, 274 NO_SAVE_UI_REASON_HAS_EMPTY_REQUIRED, 275 NO_SAVE_UI_REASON_NO_VALUE_CHANGED, 276 NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED, 277 NO_SAVE_UI_REASON_DATASET_MATCH 278 }) 279 @Retention(RetentionPolicy.SOURCE) 280 public @interface NoSaveReason{} 281 282 /** The autofill suggestion presentation is unknown, this will be set for the event 283 * that is unrelated to fill Ui presentation */ 284 public static final int UI_TYPE_UNKNOWN = 0; 285 286 /** The autofill suggestion is shown as a menu popup presentation. */ 287 public static final int UI_TYPE_MENU = 1; 288 289 /** The autofill suggestion is shown as a keyboard inline presentation. */ 290 public static final int UI_TYPE_INLINE = 2; 291 292 /** The autofill suggestion is shown as a dialog presentation. */ 293 public static final int UI_TYPE_DIALOG = 3; 294 295 /** @hide */ 296 @IntDef(prefix = { "UI_TYPE_" }, value = { 297 UI_TYPE_UNKNOWN, 298 UI_TYPE_MENU, 299 UI_TYPE_INLINE, 300 UI_TYPE_DIALOG 301 }) 302 @Retention(RetentionPolicy.SOURCE) 303 public @interface UiType {} 304 305 @EventIds private final int mEventType; 306 @Nullable private final String mDatasetId; 307 @Nullable private final Bundle mClientState; 308 309 // Note: mSelectedDatasetIds is stored as List<> instead of Set because Session already 310 // stores it as List 311 @Nullable private final List<String> mSelectedDatasetIds; 312 @Nullable private final ArraySet<String> mIgnoredDatasetIds; 313 314 @Nullable private final ArrayList<AutofillId> mChangedFieldIds; 315 @Nullable private final ArrayList<String> mChangedDatasetIds; 316 317 @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds; 318 @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds; 319 320 @Nullable private final AutofillId[] mDetectedFieldIds; 321 @Nullable private final FieldClassification[] mDetectedFieldClassifications; 322 323 @NoSaveReason private final int mSaveDialogNotShowReason; 324 325 326 @UiType 327 private final int mUiType; 328 329 /** 330 * Returns the type of the event. 331 * 332 * @return The type of the event 333 */ getType()334 public int getType() { 335 return mEventType; 336 } 337 338 /** 339 * Returns the id of dataset the id was on. 340 * 341 * @return The id of dataset, or {@code null} the event is not associated with a dataset. 342 */ getDatasetId()343 @Nullable public String getDatasetId() { 344 return mDatasetId; 345 } 346 347 /** 348 * Returns the client state from the {@link FillResponse} used to generate this event. 349 * 350 * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous 351 * {@link 352 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}, 353 * which is not necessary the same app being autofilled now. 354 */ getClientState()355 @Nullable public Bundle getClientState() { 356 return mClientState; 357 } 358 359 /** 360 * Returns which datasets were selected by the user. 361 * 362 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 363 */ getSelectedDatasetIds()364 @NonNull public Set<String> getSelectedDatasetIds() { 365 return mSelectedDatasetIds == null ? Collections.emptySet() 366 : new ArraySet<>(mSelectedDatasetIds); 367 } 368 369 /** 370 * Returns which datasets were NOT selected by the user. 371 * 372 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 373 */ getIgnoredDatasetIds()374 @NonNull public Set<String> getIgnoredDatasetIds() { 375 return mIgnoredDatasetIds == null ? Collections.emptySet() : mIgnoredDatasetIds; 376 } 377 378 /** 379 * Returns which fields in the selected datasets were changed by the user after the dataset 380 * was selected. 381 * 382 * <p>For example, server provides: 383 * 384 * <pre class="prettyprint"> 385 * FillResponse response = new FillResponse.Builder() 386 * .addDataset(new Dataset.Builder(presentation1) 387 * .setId("4815") 388 * .setValue(usernameId, AutofillValue.forText("MrPlow")) 389 * .build()) 390 * .addDataset(new Dataset.Builder(presentation2) 391 * .setId("162342") 392 * .setValue(passwordId, AutofillValue.forText("D'OH")) 393 * .build()) 394 * .build(); 395 * </pre> 396 * 397 * <p>User select both datasets (for username and password) but after the fields are 398 * autofilled, user changes them to: 399 * 400 * <pre class="prettyprint"> 401 * username = "ElBarto"; 402 * password = "AyCaramba"; 403 * </pre> 404 * 405 * <p>Then the result is the following map: 406 * 407 * <pre class="prettyprint"> 408 * usernameId => "4815" 409 * passwordId => "162342" 410 * </pre> 411 * 412 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 413 * 414 * @return map map whose key is the id of the change fields, and value is the id of 415 * dataset that has that field and was selected by the user. 416 */ getChangedFields()417 @NonNull public Map<AutofillId, String> getChangedFields() { 418 if (mChangedFieldIds == null || mChangedDatasetIds == null) { 419 return Collections.emptyMap(); 420 } 421 422 final int size = mChangedFieldIds.size(); 423 final ArrayMap<AutofillId, String> changedFields = new ArrayMap<>(size); 424 for (int i = 0; i < size; i++) { 425 changedFields.put(mChangedFieldIds.get(i), mChangedDatasetIds.get(i)); 426 } 427 return changedFields; 428 } 429 430 /** 431 * Gets the <a href="AutofillService.html#FieldClassification">field classification</a> 432 * results. 433 * 434 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the 435 * service requested {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...) 436 * field classification}. 437 */ getFieldsClassification()438 @NonNull public Map<AutofillId, FieldClassification> getFieldsClassification() { 439 if (mDetectedFieldIds == null) { 440 return Collections.emptyMap(); 441 } 442 final int size = mDetectedFieldIds.length; 443 final ArrayMap<AutofillId, FieldClassification> map = new ArrayMap<>(size); 444 for (int i = 0; i < size; i++) { 445 final AutofillId id = mDetectedFieldIds[i]; 446 final FieldClassification fc = mDetectedFieldClassifications[i]; 447 if (sVerbose) { 448 Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", fc=" + fc); 449 } 450 map.put(id, fc); 451 } 452 return map; 453 } 454 455 /** 456 * Returns which fields were available on datasets provided by the service but manually 457 * entered by the user. 458 * 459 * <p>For example, server provides: 460 * 461 * <pre class="prettyprint"> 462 * FillResponse response = new FillResponse.Builder() 463 * .addDataset(new Dataset.Builder(presentation1) 464 * .setId("4815") 465 * .setValue(usernameId, AutofillValue.forText("MrPlow")) 466 * .setValue(passwordId, AutofillValue.forText("AyCaramba")) 467 * .build()) 468 * .addDataset(new Dataset.Builder(presentation2) 469 * .setId("162342") 470 * .setValue(usernameId, AutofillValue.forText("ElBarto")) 471 * .setValue(passwordId, AutofillValue.forText("D'OH")) 472 * .build()) 473 * .addDataset(new Dataset.Builder(presentation3) 474 * .setId("108") 475 * .setValue(usernameId, AutofillValue.forText("MrPlow")) 476 * .setValue(passwordId, AutofillValue.forText("D'OH")) 477 * .build()) 478 * .build(); 479 * </pre> 480 * 481 * <p>User doesn't select a dataset but manually enters: 482 * 483 * <pre class="prettyprint"> 484 * username = "MrPlow"; 485 * password = "D'OH"; 486 * </pre> 487 * 488 * <p>Then the result is the following map: 489 * 490 * <pre class="prettyprint"> 491 * usernameId => { "4815", "108"} 492 * passwordId => { "162342", "108" } 493 * </pre> 494 * 495 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. 496 * 497 * @return map map whose key is the id of the manually-entered field, and value is the 498 * ids of the datasets that have that value but were not selected by the user. 499 */ getManuallyEnteredField()500 @NonNull public Map<AutofillId, Set<String>> getManuallyEnteredField() { 501 if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) { 502 return Collections.emptyMap(); 503 } 504 505 final int size = mManuallyFilledFieldIds.size(); 506 final Map<AutofillId, Set<String>> manuallyFilledFields = new ArrayMap<>(size); 507 for (int i = 0; i < size; i++) { 508 final AutofillId fieldId = mManuallyFilledFieldIds.get(i); 509 final ArrayList<String> datasetIds = mManuallyFilledDatasetIds.get(i); 510 manuallyFilledFields.put(fieldId, new ArraySet<>(datasetIds)); 511 } 512 return manuallyFilledFields; 513 } 514 515 /** 516 * Returns the reason why a save dialog was not shown. 517 * 518 * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. For the other 519 * event types, the reason is set to NO_SAVE_UI_REASON_NONE. 520 * 521 * @return The reason why a save dialog was not shown. 522 */ 523 @NoSaveReason getNoSaveUiReason()524 public int getNoSaveUiReason() { 525 return mSaveDialogNotShowReason; 526 } 527 528 /** 529 * Returns fill suggestion ui presentation type which corresponds to types 530 * defined in {@link android.service.autofill.Presentations). 531 * 532 * <p><b>Note: </b>Only set on events of type {@link #TYPE_DATASETS_SHOWN} and 533 * {@link #TYPE_DATASET_SELECTED}. For the other event types, the type is set to 534 * {@link #UI_TYPE_UNKNOWN }. 535 * 536 * @return The ui presentation type shown for user. 537 */ 538 @UiType getUiType()539 public int getUiType() { 540 return mUiType; 541 } 542 543 /** 544 * Creates a new event. 545 * 546 * @param eventType The type of the event 547 * @param datasetId The dataset the event was on, or {@code null} if the event was on the 548 * whole response. 549 * @param clientState The client state associated with the event. 550 * @param selectedDatasetIds The ids of datasets selected by the user. 551 * @param ignoredDatasetIds The ids of datasets NOT select by the user. 552 * @param changedFieldIds The ids of fields changed by the user. 553 * @param changedDatasetIds The ids of the datasets that havd values matching the 554 * respective entry on {@code changedFieldIds}. 555 * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user 556 * and belonged to datasets. 557 * @param manuallyFilledDatasetIds The ids of datasets that had values matching the 558 * respective entry on {@code manuallyFilledFieldIds}. 559 * @param detectedFieldClassifications the field classification matches. 560 * 561 * @throws IllegalArgumentException If the length of {@code changedFieldIds} and 562 * {@code changedDatasetIds} doesn't match. 563 * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and 564 * {@code manuallyFilledDatasetIds} doesn't match. 565 * 566 * @hide 567 */ 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)568 public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, 569 @Nullable List<String> selectedDatasetIds, 570 @Nullable ArraySet<String> ignoredDatasetIds, 571 @Nullable ArrayList<AutofillId> changedFieldIds, 572 @Nullable ArrayList<String> changedDatasetIds, 573 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, 574 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, 575 @Nullable AutofillId[] detectedFieldIds, 576 @Nullable FieldClassification[] detectedFieldClassifications) { 577 this(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasetIds, 578 changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, 579 manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications, 580 NO_SAVE_UI_REASON_NONE); 581 } 582 583 /** 584 * Creates a new event. 585 * 586 * @param eventType The type of the event 587 * @param datasetId The dataset the event was on, or {@code null} if the event was on the 588 * whole response. 589 * @param clientState The client state associated with the event. 590 * @param selectedDatasetIds The ids of datasets selected by the user. 591 * @param ignoredDatasetIds The ids of datasets NOT select by the user. 592 * @param changedFieldIds The ids of fields changed by the user. 593 * @param changedDatasetIds The ids of the datasets that havd values matching the 594 * respective entry on {@code changedFieldIds}. 595 * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user 596 * and belonged to datasets. 597 * @param manuallyFilledDatasetIds The ids of datasets that had values matching the 598 * respective entry on {@code manuallyFilledFieldIds}. 599 * @param detectedFieldClassifications the field classification matches. 600 * @param saveDialogNotShowReason The reason why a save dialog was not shown. 601 * 602 * @throws IllegalArgumentException If the length of {@code changedFieldIds} and 603 * {@code changedDatasetIds} doesn't match. 604 * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and 605 * {@code manuallyFilledDatasetIds} doesn't match. 606 * 607 * @hide 608 */ 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)609 public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, 610 @Nullable List<String> selectedDatasetIds, 611 @Nullable ArraySet<String> ignoredDatasetIds, 612 @Nullable ArrayList<AutofillId> changedFieldIds, 613 @Nullable ArrayList<String> changedDatasetIds, 614 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, 615 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, 616 @Nullable AutofillId[] detectedFieldIds, 617 @Nullable FieldClassification[] detectedFieldClassifications, 618 int saveDialogNotShowReason) { 619 this(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasetIds, 620 changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, 621 manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications, 622 saveDialogNotShowReason, UI_TYPE_UNKNOWN); 623 } 624 625 /** 626 * Creates a new event. 627 * 628 * @param eventType The type of the event 629 * @param datasetId The dataset the event was on, or {@code null} if the event was on the 630 * whole response. 631 * @param clientState The client state associated with the event. 632 * @param selectedDatasetIds The ids of datasets selected by the user. 633 * @param ignoredDatasetIds The ids of datasets NOT select by the user. 634 * @param changedFieldIds The ids of fields changed by the user. 635 * @param changedDatasetIds The ids of the datasets that havd values matching the 636 * respective entry on {@code changedFieldIds}. 637 * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user 638 * and belonged to datasets. 639 * @param manuallyFilledDatasetIds The ids of datasets that had values matching the 640 * respective entry on {@code manuallyFilledFieldIds}. 641 * @param detectedFieldClassifications the field classification matches. 642 * @param saveDialogNotShowReason The reason why a save dialog was not shown. 643 * @param uiType The ui presentation type for fill suggestion. 644 * 645 * @throws IllegalArgumentException If the length of {@code changedFieldIds} and 646 * {@code changedDatasetIds} doesn't match. 647 * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and 648 * {@code manuallyFilledDatasetIds} doesn't match. 649 * 650 * @hide 651 */ 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)652 public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState, 653 @Nullable List<String> selectedDatasetIds, 654 @Nullable ArraySet<String> ignoredDatasetIds, 655 @Nullable ArrayList<AutofillId> changedFieldIds, 656 @Nullable ArrayList<String> changedDatasetIds, 657 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, 658 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, 659 @Nullable AutofillId[] detectedFieldIds, 660 @Nullable FieldClassification[] detectedFieldClassifications, 661 int saveDialogNotShowReason, int uiType) { 662 mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_DATASETS_SHOWN, 663 "eventType"); 664 mDatasetId = datasetId; 665 mClientState = clientState; 666 mSelectedDatasetIds = selectedDatasetIds; 667 mIgnoredDatasetIds = ignoredDatasetIds; 668 if (changedFieldIds != null) { 669 Preconditions.checkArgument(!ArrayUtils.isEmpty(changedFieldIds) 670 && changedDatasetIds != null 671 && changedFieldIds.size() == changedDatasetIds.size(), 672 "changed ids must have same length and not be empty"); 673 } 674 mChangedFieldIds = changedFieldIds; 675 mChangedDatasetIds = changedDatasetIds; 676 if (manuallyFilledFieldIds != null) { 677 Preconditions.checkArgument(!ArrayUtils.isEmpty(manuallyFilledFieldIds) 678 && manuallyFilledDatasetIds != null 679 && manuallyFilledFieldIds.size() == manuallyFilledDatasetIds.size(), 680 "manually filled ids must have same length and not be empty"); 681 } 682 mManuallyFilledFieldIds = manuallyFilledFieldIds; 683 mManuallyFilledDatasetIds = manuallyFilledDatasetIds; 684 685 mDetectedFieldIds = detectedFieldIds; 686 mDetectedFieldClassifications = detectedFieldClassifications; 687 688 mSaveDialogNotShowReason = Preconditions.checkArgumentInRange(saveDialogNotShowReason, 689 NO_SAVE_UI_REASON_NONE, NO_SAVE_UI_REASON_DATASET_MATCH, 690 "saveDialogNotShowReason"); 691 mUiType = uiType; 692 } 693 694 @Override toString()695 public String toString() { 696 return "FillEvent [datasetId=" + mDatasetId 697 + ", type=" + eventToString(mEventType) 698 + ", uiType=" + uiTypeToString(mUiType) 699 + ", selectedDatasets=" + mSelectedDatasetIds 700 + ", ignoredDatasetIds=" + mIgnoredDatasetIds 701 + ", changedFieldIds=" + mChangedFieldIds 702 + ", changedDatasetsIds=" + mChangedDatasetIds 703 + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds 704 + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds 705 + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds) 706 + ", detectedFieldClassifications =" 707 + Arrays.toString(mDetectedFieldClassifications) 708 + ", saveDialogNotShowReason=" + mSaveDialogNotShowReason 709 + "]"; 710 } 711 eventToString(int eventType)712 private static String eventToString(int eventType) { 713 switch (eventType) { 714 case TYPE_DATASET_SELECTED: 715 return "TYPE_DATASET_SELECTED"; 716 case TYPE_DATASET_AUTHENTICATION_SELECTED: 717 return "TYPE_DATASET_AUTHENTICATION_SELECTED"; 718 case TYPE_AUTHENTICATION_SELECTED: 719 return "TYPE_AUTHENTICATION_SELECTED"; 720 case TYPE_SAVE_SHOWN: 721 return "TYPE_SAVE_SHOWN"; 722 case TYPE_CONTEXT_COMMITTED: 723 return "TYPE_CONTEXT_COMMITTED"; 724 case TYPE_DATASETS_SHOWN: 725 return "TYPE_DATASETS_SHOWN"; 726 default: 727 return "TYPE_UNKNOWN"; 728 } 729 } 730 uiTypeToString(int uiType)731 private static String uiTypeToString(int uiType) { 732 switch (uiType) { 733 case UI_TYPE_MENU: 734 return "UI_TYPE_MENU"; 735 case UI_TYPE_INLINE: 736 return "UI_TYPE_INLINE"; 737 case UI_TYPE_DIALOG: 738 return "UI_TYPE_FILL_DIALOG"; 739 default: 740 return "UI_TYPE_UNKNOWN"; 741 } 742 } 743 } 744 745 public static final @android.annotation.NonNull Parcelable.Creator<FillEventHistory> CREATOR = 746 new Parcelable.Creator<FillEventHistory>() { 747 @Override 748 public FillEventHistory createFromParcel(Parcel parcel) { 749 FillEventHistory selection = new FillEventHistory(0, parcel.readBundle()); 750 751 final int numEvents = parcel.readInt(); 752 for (int i = 0; i < numEvents; i++) { 753 final int eventType = parcel.readInt(); 754 final String datasetId = parcel.readString(); 755 final Bundle clientState = parcel.readBundle(); 756 final ArrayList<String> selectedDatasetIds = parcel.createStringArrayList(); 757 @SuppressWarnings("unchecked") 758 final ArraySet<String> ignoredDatasets = 759 (ArraySet<String>) parcel.readArraySet(null); 760 final ArrayList<AutofillId> changedFieldIds = 761 parcel.createTypedArrayList(AutofillId.CREATOR); 762 final ArrayList<String> changedDatasetIds = parcel.createStringArrayList(); 763 764 final ArrayList<AutofillId> manuallyFilledFieldIds = 765 parcel.createTypedArrayList(AutofillId.CREATOR); 766 final ArrayList<ArrayList<String>> manuallyFilledDatasetIds; 767 if (manuallyFilledFieldIds != null) { 768 final int size = manuallyFilledFieldIds.size(); 769 manuallyFilledDatasetIds = new ArrayList<>(size); 770 for (int j = 0; j < size; j++) { 771 manuallyFilledDatasetIds.add(parcel.createStringArrayList()); 772 } 773 } else { 774 manuallyFilledDatasetIds = null; 775 } 776 final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null, 777 AutofillId.class); 778 final FieldClassification[] detectedFieldClassifications = 779 (detectedFieldIds != null) 780 ? FieldClassification.readArrayFromParcel(parcel) 781 : null; 782 final int saveDialogNotShowReason = parcel.readInt(); 783 final int uiType = parcel.readInt(); 784 785 selection.addEvent(new Event(eventType, datasetId, clientState, 786 selectedDatasetIds, ignoredDatasets, 787 changedFieldIds, changedDatasetIds, 788 manuallyFilledFieldIds, manuallyFilledDatasetIds, 789 detectedFieldIds, detectedFieldClassifications, 790 saveDialogNotShowReason, uiType)); 791 } 792 return selection; 793 } 794 795 @Override 796 public FillEventHistory[] newArray(int size) { 797 return new FillEventHistory[size]; 798 } 799 }; 800 } 801