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