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