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