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             }
164         }
165     }
166 
167     /**
168      * Description of an event that occured after the latest call to
169      * {@link FillCallback#onSuccess(FillResponse)}.
170      */
171     public static final class Event {
172         /**
173          * A dataset was selected. The dataset selected can be read from {@link #getDatasetId()}.
174          *
175          * <p><b>Note: </b>on Android {@link android.os.Build.VERSION_CODES#O}, this event was also
176          * incorrectly reported after a
177          * {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was
178          * selected and the service returned a dataset in the
179          * {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT} of the activity launched from that
180          * {@link IntentSender}. This behavior was fixed on Android
181          * {@link android.os.Build.VERSION_CODES#O_MR1}.
182          */
183         public static final int TYPE_DATASET_SELECTED = 0;
184 
185         /**
186          * A {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was
187          * selected. The dataset authenticated can be read from {@link #getDatasetId()}.
188          */
189         public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1;
190 
191         /**
192          * A {@link FillResponse.Builder#setAuthentication(android.view.autofill.AutofillId[],
193          * IntentSender, android.widget.RemoteViews) fill response authentication} was selected.
194          */
195         public static final int TYPE_AUTHENTICATION_SELECTED = 2;
196 
197         /** A save UI was shown. */
198         public static final int TYPE_SAVE_SHOWN = 3;
199 
200         /**
201          * A committed autofill context for which the autofill service provided datasets.
202          *
203          * <p>This event is useful to track:
204          * <ul>
205          *   <li>Which datasets (if any) were selected by the user
206          *       ({@link #getSelectedDatasetIds()}).
207          *   <li>Which datasets (if any) were NOT selected by the user
208          *       ({@link #getIgnoredDatasetIds()}).
209          *   <li>Which fields in the selected datasets were changed by the user after the dataset
210          *       was selected ({@link #getChangedFields()}.
211          *   <li>Which fields match the {@link UserData} set by the service.
212          * </ul>
213          *
214          * <p><b>Note: </b>This event is only generated when:
215          * <ul>
216          *   <li>The autofill context is committed.
217          *   <li>The service provides at least one dataset in the
218          *       {@link FillResponse fill responses} associated with the context.
219          *   <li>The last {@link FillResponse fill responses} associated with the context has the
220          *       {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} flag.
221          * </ul>
222          *
223          * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
224          * contexts.
225          */
226         public static final int TYPE_CONTEXT_COMMITTED = 4;
227 
228         /**
229          * A dataset selector was shown.
230          *
231          * <p>This event is fired whenever the autofill UI was presented to the user.</p>
232          */
233         public static final int TYPE_DATASETS_SHOWN = 5;
234 
235         /** @hide */
236         @IntDef(prefix = { "TYPE_" }, value = {
237                 TYPE_DATASET_SELECTED,
238                 TYPE_DATASET_AUTHENTICATION_SELECTED,
239                 TYPE_AUTHENTICATION_SELECTED,
240                 TYPE_SAVE_SHOWN,
241                 TYPE_CONTEXT_COMMITTED,
242                 TYPE_DATASETS_SHOWN
243         })
244         @Retention(RetentionPolicy.SOURCE)
245         @interface EventIds{}
246 
247         /** No reason for save dialog. */
248         public static final int NO_SAVE_UI_REASON_NONE = 0;
249 
250         /** The SaveInfo associated with the FillResponse is null. */
251         public static final int NO_SAVE_UI_REASON_NO_SAVE_INFO = 1;
252 
253         /** The service asked to delay save. */
254         public static final int NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG = 2;
255 
256         /** There was empty value for required ids. */
257         public static final int NO_SAVE_UI_REASON_HAS_EMPTY_REQUIRED = 3;
258 
259         /** No value has been changed. */
260         public static final int NO_SAVE_UI_REASON_NO_VALUE_CHANGED = 4;
261 
262         /** Fields failed validation. */
263         public static final int NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED = 5;
264 
265         /** All fields matched contents of datasets. */
266         public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6;
267 
268         /** @hide */
269         @IntDef(prefix = { "NO_SAVE_UI_REASON_" }, value = {
270                 NO_SAVE_UI_REASON_NONE,
271                 NO_SAVE_UI_REASON_NO_SAVE_INFO,
272                 NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG,
273                 NO_SAVE_UI_REASON_HAS_EMPTY_REQUIRED,
274                 NO_SAVE_UI_REASON_NO_VALUE_CHANGED,
275                 NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED,
276                 NO_SAVE_UI_REASON_DATASET_MATCH
277         })
278         @Retention(RetentionPolicy.SOURCE)
279         public @interface NoSaveReason{}
280 
281         @EventIds private final int mEventType;
282         @Nullable private final String mDatasetId;
283         @Nullable private final Bundle mClientState;
284 
285         // Note: mSelectedDatasetIds is stored as List<> instead of Set because Session already
286         // stores it as List
287         @Nullable private final List<String> mSelectedDatasetIds;
288         @Nullable private final ArraySet<String> mIgnoredDatasetIds;
289 
290         @Nullable private final ArrayList<AutofillId> mChangedFieldIds;
291         @Nullable private final ArrayList<String> mChangedDatasetIds;
292 
293         @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
294         @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
295 
296         @Nullable private final AutofillId[] mDetectedFieldIds;
297         @Nullable private final FieldClassification[] mDetectedFieldClassifications;
298 
299         @NoSaveReason private final int mSaveDialogNotShowReason;
300 
301         /**
302          * Returns the type of the event.
303          *
304          * @return The type of the event
305          */
getType()306         public int getType() {
307             return mEventType;
308         }
309 
310         /**
311          * Returns the id of dataset the id was on.
312          *
313          * @return The id of dataset, or {@code null} the event is not associated with a dataset.
314          */
getDatasetId()315         @Nullable public String getDatasetId() {
316             return mDatasetId;
317         }
318 
319         /**
320          * Returns the client state from the {@link FillResponse} used to generate this event.
321          *
322          * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous
323          * {@link
324          * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)},
325          * which is not necessary the same app being autofilled now.
326          */
getClientState()327         @Nullable public Bundle getClientState() {
328             return mClientState;
329         }
330 
331         /**
332          * Returns which datasets were selected by the user.
333          *
334          * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
335          */
getSelectedDatasetIds()336         @NonNull public Set<String> getSelectedDatasetIds() {
337             return mSelectedDatasetIds == null ? Collections.emptySet()
338                     : new ArraySet<>(mSelectedDatasetIds);
339         }
340 
341         /**
342          * Returns which datasets were NOT selected by the user.
343          *
344          * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
345          */
getIgnoredDatasetIds()346         @NonNull public Set<String> getIgnoredDatasetIds() {
347             return mIgnoredDatasetIds == null ? Collections.emptySet() : mIgnoredDatasetIds;
348         }
349 
350         /**
351          * Returns which fields in the selected datasets were changed by the user after the dataset
352          * was selected.
353          *
354          * <p>For example, server provides:
355          *
356          * <pre class="prettyprint">
357          *  FillResponse response = new FillResponse.Builder()
358          *      .addDataset(new Dataset.Builder(presentation1)
359          *          .setId("4815")
360          *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
361          *          .build())
362          *      .addDataset(new Dataset.Builder(presentation2)
363          *          .setId("162342")
364          *          .setValue(passwordId, AutofillValue.forText("D'OH"))
365          *          .build())
366          *      .build();
367          * </pre>
368          *
369          * <p>User select both datasets (for username and password) but after the fields are
370          * autofilled, user changes them to:
371          *
372          * <pre class="prettyprint">
373          *   username = "ElBarto";
374          *   password = "AyCaramba";
375          * </pre>
376          *
377          * <p>Then the result is the following map:
378          *
379          * <pre class="prettyprint">
380          *   usernameId => "4815"
381          *   passwordId => "162342"
382          * </pre>
383          *
384          * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
385          *
386          * @return map map whose key is the id of the change fields, and value is the id of
387          * dataset that has that field and was selected by the user.
388          */
getChangedFields()389         @NonNull public Map<AutofillId, String> getChangedFields() {
390             if (mChangedFieldIds == null || mChangedDatasetIds == null) {
391                 return Collections.emptyMap();
392             }
393 
394             final int size = mChangedFieldIds.size();
395             final ArrayMap<AutofillId, String> changedFields = new ArrayMap<>(size);
396             for (int i = 0; i < size; i++) {
397                 changedFields.put(mChangedFieldIds.get(i), mChangedDatasetIds.get(i));
398             }
399             return changedFields;
400         }
401 
402         /**
403          * Gets the <a href="AutofillService.html#FieldClassification">field classification</a>
404          * results.
405          *
406          * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the
407          * service requested {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...)
408          * field classification}.
409          */
getFieldsClassification()410         @NonNull public Map<AutofillId, FieldClassification> getFieldsClassification() {
411             if (mDetectedFieldIds == null) {
412                 return Collections.emptyMap();
413             }
414             final int size = mDetectedFieldIds.length;
415             final ArrayMap<AutofillId, FieldClassification> map = new ArrayMap<>(size);
416             for (int i = 0; i < size; i++) {
417                 final AutofillId id = mDetectedFieldIds[i];
418                 final FieldClassification fc = mDetectedFieldClassifications[i];
419                 if (sVerbose) {
420                     Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", fc=" + fc);
421                 }
422                 map.put(id, fc);
423             }
424             return map;
425         }
426 
427         /**
428          * Returns which fields were available on datasets provided by the service but manually
429          * entered by the user.
430          *
431          * <p>For example, server provides:
432          *
433          * <pre class="prettyprint">
434          *  FillResponse response = new FillResponse.Builder()
435          *      .addDataset(new Dataset.Builder(presentation1)
436          *          .setId("4815")
437          *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
438          *          .setValue(passwordId, AutofillValue.forText("AyCaramba"))
439          *          .build())
440          *      .addDataset(new Dataset.Builder(presentation2)
441          *          .setId("162342")
442          *          .setValue(usernameId, AutofillValue.forText("ElBarto"))
443          *          .setValue(passwordId, AutofillValue.forText("D'OH"))
444          *          .build())
445          *      .addDataset(new Dataset.Builder(presentation3)
446          *          .setId("108")
447          *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
448          *          .setValue(passwordId, AutofillValue.forText("D'OH"))
449          *          .build())
450          *      .build();
451          * </pre>
452          *
453          * <p>User doesn't select a dataset but manually enters:
454          *
455          * <pre class="prettyprint">
456          *   username = "MrPlow";
457          *   password = "D'OH";
458          * </pre>
459          *
460          * <p>Then the result is the following map:
461          *
462          * <pre class="prettyprint">
463          *   usernameId => { "4815", "108"}
464          *   passwordId => { "162342", "108" }
465          * </pre>
466          *
467          * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
468          *
469          * @return map map whose key is the id of the manually-entered field, and value is the
470          * ids of the datasets that have that value but were not selected by the user.
471          */
getManuallyEnteredField()472         @NonNull public Map<AutofillId, Set<String>> getManuallyEnteredField() {
473             if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) {
474                 return Collections.emptyMap();
475             }
476 
477             final int size = mManuallyFilledFieldIds.size();
478             final Map<AutofillId, Set<String>> manuallyFilledFields = new ArrayMap<>(size);
479             for (int i = 0; i < size; i++) {
480                 final AutofillId fieldId = mManuallyFilledFieldIds.get(i);
481                 final ArrayList<String> datasetIds = mManuallyFilledDatasetIds.get(i);
482                 manuallyFilledFields.put(fieldId, new ArraySet<>(datasetIds));
483             }
484             return manuallyFilledFields;
485         }
486 
487         /**
488          * Returns the reason why a save dialog was not shown.
489          *
490          * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}. For the other
491          * event types, the reason is set to NO_SAVE_UI_REASON_NONE.
492          *
493          * @return The reason why a save dialog was not shown.
494          */
495         @NoSaveReason
getNoSaveUiReason()496         public int getNoSaveUiReason() {
497             return mSaveDialogNotShowReason;
498         }
499 
500         /**
501          * Creates a new event.
502          *
503          * @param eventType The type of the event
504          * @param datasetId The dataset the event was on, or {@code null} if the event was on the
505          *                  whole response.
506          * @param clientState The client state associated with the event.
507          * @param selectedDatasetIds The ids of datasets selected by the user.
508          * @param ignoredDatasetIds The ids of datasets NOT select by the user.
509          * @param changedFieldIds The ids of fields changed by the user.
510          * @param changedDatasetIds The ids of the datasets that havd values matching the
511          * respective entry on {@code changedFieldIds}.
512          * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user
513          * and belonged to datasets.
514          * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
515          * respective entry on {@code manuallyFilledFieldIds}.
516          * @param detectedFieldClassifications the field classification matches.
517          *
518          * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
519          * {@code changedDatasetIds} doesn't match.
520          * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
521          * {@code manuallyFilledDatasetIds} doesn't match.
522          *
523          * @hide
524          */
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)525         public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
526                 @Nullable List<String> selectedDatasetIds,
527                 @Nullable ArraySet<String> ignoredDatasetIds,
528                 @Nullable ArrayList<AutofillId> changedFieldIds,
529                 @Nullable ArrayList<String> changedDatasetIds,
530                 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
531                 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
532                 @Nullable AutofillId[] detectedFieldIds,
533                 @Nullable FieldClassification[] detectedFieldClassifications) {
534             this(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasetIds,
535                     changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
536                     manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
537                     NO_SAVE_UI_REASON_NONE);
538         }
539 
540         /**
541          * Creates a new event.
542          *
543          * @param eventType The type of the event
544          * @param datasetId The dataset the event was on, or {@code null} if the event was on the
545          *                  whole response.
546          * @param clientState The client state associated with the event.
547          * @param selectedDatasetIds The ids of datasets selected by the user.
548          * @param ignoredDatasetIds The ids of datasets NOT select by the user.
549          * @param changedFieldIds The ids of fields changed by the user.
550          * @param changedDatasetIds The ids of the datasets that havd values matching the
551          * respective entry on {@code changedFieldIds}.
552          * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user
553          * and belonged to datasets.
554          * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
555          * respective entry on {@code manuallyFilledFieldIds}.
556          * @param detectedFieldClassifications the field classification matches.
557          * @param saveDialogNotShowReason The reason why a save dialog was not shown.
558          *
559          * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
560          * {@code changedDatasetIds} doesn't match.
561          * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
562          * {@code manuallyFilledDatasetIds} doesn't match.
563          *
564          * @hide
565          */
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)566         public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
567                 @Nullable List<String> selectedDatasetIds,
568                 @Nullable ArraySet<String> ignoredDatasetIds,
569                 @Nullable ArrayList<AutofillId> changedFieldIds,
570                 @Nullable ArrayList<String> changedDatasetIds,
571                 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
572                 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
573                 @Nullable AutofillId[] detectedFieldIds,
574                 @Nullable FieldClassification[] detectedFieldClassifications,
575                 int saveDialogNotShowReason) {
576             mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_DATASETS_SHOWN,
577                     "eventType");
578             mDatasetId = datasetId;
579             mClientState = clientState;
580             mSelectedDatasetIds = selectedDatasetIds;
581             mIgnoredDatasetIds = ignoredDatasetIds;
582             if (changedFieldIds != null) {
583                 Preconditions.checkArgument(!ArrayUtils.isEmpty(changedFieldIds)
584                         && changedDatasetIds != null
585                         && changedFieldIds.size() == changedDatasetIds.size(),
586                         "changed ids must have same length and not be empty");
587             }
588             mChangedFieldIds = changedFieldIds;
589             mChangedDatasetIds = changedDatasetIds;
590             if (manuallyFilledFieldIds != null) {
591                 Preconditions.checkArgument(!ArrayUtils.isEmpty(manuallyFilledFieldIds)
592                         && manuallyFilledDatasetIds != null
593                         && manuallyFilledFieldIds.size() == manuallyFilledDatasetIds.size(),
594                         "manually filled ids must have same length and not be empty");
595             }
596             mManuallyFilledFieldIds = manuallyFilledFieldIds;
597             mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
598 
599             mDetectedFieldIds = detectedFieldIds;
600             mDetectedFieldClassifications = detectedFieldClassifications;
601 
602             mSaveDialogNotShowReason = Preconditions.checkArgumentInRange(saveDialogNotShowReason,
603                     NO_SAVE_UI_REASON_NONE, NO_SAVE_UI_REASON_DATASET_MATCH,
604                     "saveDialogNotShowReason");
605         }
606 
607         @Override
toString()608         public String toString() {
609             return "FillEvent [datasetId=" + mDatasetId
610                     + ", type=" + mEventType
611                     + ", selectedDatasets=" + mSelectedDatasetIds
612                     + ", ignoredDatasetIds=" + mIgnoredDatasetIds
613                     + ", changedFieldIds=" + mChangedFieldIds
614                     + ", changedDatasetsIds=" + mChangedDatasetIds
615                     + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
616                     + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
617                     + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds)
618                     + ", detectedFieldClassifications ="
619                         + Arrays.toString(mDetectedFieldClassifications)
620                     + ", saveDialogNotShowReason=" + mSaveDialogNotShowReason
621                     + "]";
622         }
623     }
624 
625     public static final @android.annotation.NonNull Parcelable.Creator<FillEventHistory> CREATOR =
626             new Parcelable.Creator<FillEventHistory>() {
627                 @Override
628                 public FillEventHistory createFromParcel(Parcel parcel) {
629                     FillEventHistory selection = new FillEventHistory(0, parcel.readBundle());
630 
631                     final int numEvents = parcel.readInt();
632                     for (int i = 0; i < numEvents; i++) {
633                         final int eventType = parcel.readInt();
634                         final String datasetId = parcel.readString();
635                         final Bundle clientState = parcel.readBundle();
636                         final ArrayList<String> selectedDatasetIds = parcel.createStringArrayList();
637                         @SuppressWarnings("unchecked")
638                         final ArraySet<String> ignoredDatasets =
639                                 (ArraySet<String>) parcel.readArraySet(null);
640                         final ArrayList<AutofillId> changedFieldIds =
641                                 parcel.createTypedArrayList(AutofillId.CREATOR);
642                         final ArrayList<String> changedDatasetIds = parcel.createStringArrayList();
643 
644                         final ArrayList<AutofillId> manuallyFilledFieldIds =
645                                 parcel.createTypedArrayList(AutofillId.CREATOR);
646                         final ArrayList<ArrayList<String>> manuallyFilledDatasetIds;
647                         if (manuallyFilledFieldIds != null) {
648                             final int size = manuallyFilledFieldIds.size();
649                             manuallyFilledDatasetIds = new ArrayList<>(size);
650                             for (int j = 0; j < size; j++) {
651                                 manuallyFilledDatasetIds.add(parcel.createStringArrayList());
652                             }
653                         } else {
654                             manuallyFilledDatasetIds = null;
655                         }
656                         final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null,
657                                 AutofillId.class);
658                         final FieldClassification[] detectedFieldClassifications =
659                                 (detectedFieldIds != null)
660                                 ? FieldClassification.readArrayFromParcel(parcel)
661                                 : null;
662                         final int saveDialogNotShowReason = parcel.readInt();
663 
664                         selection.addEvent(new Event(eventType, datasetId, clientState,
665                                 selectedDatasetIds, ignoredDatasets,
666                                 changedFieldIds, changedDatasetIds,
667                                 manuallyFilledFieldIds, manuallyFilledDatasetIds,
668                                 detectedFieldIds, detectedFieldClassifications,
669                                 saveDialogNotShowReason));
670                     }
671                     return selection;
672                 }
673 
674                 @Override
675                 public FillEventHistory[] newArray(int size) {
676                     return new FillEventHistory[size];
677                 }
678             };
679 }
680