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