Home | History | Annotate | Download | only in autofill
      1 /*
      2  * Copyright (C) 2016 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.sDebug;
     20 
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.content.IntentSender;
     24 import android.os.Parcel;
     25 import android.os.Parcelable;
     26 import android.view.autofill.AutofillId;
     27 import android.view.autofill.AutofillValue;
     28 import android.widget.RemoteViews;
     29 
     30 import com.android.internal.util.Preconditions;
     31 
     32 import java.util.ArrayList;
     33 
     34 /**
     35  * A dataset object represents a group of key/value pairs used to autofill parts of a screen.
     36  *
     37  * <p>In its simplest form, a dataset contains one or more key / value pairs (comprised of
     38  * {@link AutofillId} and {@link AutofillValue} respectively); and one or more
     39  * {@link RemoteViews presentation} for these pairs (a pair could have its own
     40  * {@link RemoteViews presentation}, or use the default {@link RemoteViews presentation} associated
     41  * with the whole dataset). When an autofill service returns datasets in a {@link FillResponse}
     42  * and the screen input is focused in a view that is present in at least one of these datasets,
     43  * the Android System displays a UI affordance containing the {@link RemoteViews presentation} of
     44  * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
     45  * dataset from the affordance, all views in that dataset are autofilled.
     46  *
     47  * <p>In a more sophisticated form, the dataset value can be protected until the user authenticates
     48  * the dataset - see {@link Dataset.Builder#setAuthentication(IntentSender)}.
     49  *
     50  * @see android.service.autofill.AutofillService for more information and examples about the
     51  * role of datasets in the autofill workflow.
     52  */
     53 public final class Dataset implements Parcelable {
     54 
     55     private final ArrayList<AutofillId> mFieldIds;
     56     private final ArrayList<AutofillValue> mFieldValues;
     57     private final ArrayList<RemoteViews> mFieldPresentations;
     58     private final RemoteViews mPresentation;
     59     private final IntentSender mAuthentication;
     60     @Nullable String mId;
     61 
     62     private Dataset(Builder builder) {
     63         mFieldIds = builder.mFieldIds;
     64         mFieldValues = builder.mFieldValues;
     65         mFieldPresentations = builder.mFieldPresentations;
     66         mPresentation = builder.mPresentation;
     67         mAuthentication = builder.mAuthentication;
     68         mId = builder.mId;
     69     }
     70 
     71     /** @hide */
     72     public @Nullable ArrayList<AutofillId> getFieldIds() {
     73         return mFieldIds;
     74     }
     75 
     76     /** @hide */
     77     public @Nullable ArrayList<AutofillValue> getFieldValues() {
     78         return mFieldValues;
     79     }
     80 
     81     /** @hide */
     82     public RemoteViews getFieldPresentation(int index) {
     83         final RemoteViews customPresentation = mFieldPresentations.get(index);
     84         return customPresentation != null ? customPresentation : mPresentation;
     85     }
     86 
     87     /** @hide */
     88     public @Nullable IntentSender getAuthentication() {
     89         return mAuthentication;
     90     }
     91 
     92     /** @hide */
     93     public boolean isEmpty() {
     94         return mFieldIds == null || mFieldIds.isEmpty();
     95     }
     96 
     97     @Override
     98     public String toString() {
     99         if (!sDebug) return super.toString();
    100 
    101         return new StringBuilder("Dataset " + mId + " [")
    102                 .append("fieldIds=").append(mFieldIds)
    103                 .append(", fieldValues=").append(mFieldValues)
    104                 .append(", fieldPresentations=")
    105                 .append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
    106                 .append(", hasPresentation=").append(mPresentation != null)
    107                 .append(", hasAuthentication=").append(mAuthentication != null)
    108                 .append(']').toString();
    109     }
    110 
    111     /**
    112      * Gets the id of this dataset.
    113      *
    114      * @return The id of this dataset or {@code null} if not set
    115      *
    116      * @hide
    117      */
    118     public String getId() {
    119         return mId;
    120     }
    121 
    122     /**
    123      * A builder for {@link Dataset} objects. You must provide at least
    124      * one value for a field or set an authentication intent.
    125      */
    126     public static final class Builder {
    127         private ArrayList<AutofillId> mFieldIds;
    128         private ArrayList<AutofillValue> mFieldValues;
    129         private ArrayList<RemoteViews> mFieldPresentations;
    130         private RemoteViews mPresentation;
    131         private IntentSender mAuthentication;
    132         private boolean mDestroyed;
    133         @Nullable private String mId;
    134 
    135         /**
    136          * Creates a new builder.
    137          *
    138          * @param presentation The presentation used to visualize this dataset.
    139          */
    140         public Builder(@NonNull RemoteViews presentation) {
    141             Preconditions.checkNotNull(presentation, "presentation must be non-null");
    142             mPresentation = presentation;
    143         }
    144 
    145         /**
    146          * Creates a new builder for a dataset where each field will be visualized independently.
    147          *
    148          * <p>When using this constructor, fields must be set through
    149          * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}.
    150          */
    151         public Builder() {
    152         }
    153 
    154         /**
    155          * Requires a dataset authentication before autofilling the activity with this dataset.
    156          *
    157          * <p>This method is called when you need to provide an authentication
    158          * UI for the data set. For example, when a data set contains credit card information
    159          * (such as number, expiration date, and verification code), you can display UI
    160          * asking for the verification code before filing in the data. Even if the
    161          * data set is completely populated the system will launch the specified authentication
    162          * intent and will need your approval to fill it in. Since the data set is "locked"
    163          * until the user authenticates it, typically this data set name is masked
    164          * (for example, "VISA....1234"). Typically you would want to store the data set
    165          * labels non-encrypted and the actual sensitive data encrypted and not in memory.
    166          * This allows showing the labels in the UI while involving the user if one of
    167          * the items with these labels is chosen. Note that if you use sensitive data as
    168          * a label, for example an email address, then it should also be encrypted.</p>
    169          *
    170          * <p>When a user triggers autofill, the system launches the provided intent
    171          * whose extras will have the {@link
    172          * android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen content},
    173          * and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE client
    174          * state}. Once you complete your authentication flow you should set the activity
    175          * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
    176          * {@link Dataset dataset} or a fully-populated {@link FillResponse response} by
    177          * setting it to the {@link
    178          * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you
    179          * provide a dataset in the result, it will replace the authenticated dataset and
    180          * will be immediately filled in. If you provide a response, it will replace the
    181          * current response and the UI will be refreshed. For example, if you provided
    182          * credit card information without the CVV for the data set in the {@link FillResponse
    183          * response} then the returned data set should contain the CVV entry.
    184          *
    185          * <p><b>NOTE:</b> Do not make the provided pending intent
    186          * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
    187          * platform needs to fill in the authentication arguments.
    188          *
    189          * @param authentication Intent to an activity with your authentication flow.
    190          * @return This builder.
    191          *
    192          * @see android.app.PendingIntent
    193          */
    194         public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) {
    195             throwIfDestroyed();
    196             mAuthentication = authentication;
    197             return this;
    198         }
    199 
    200         /**
    201          * Sets the id for the dataset so its usage history can be retrieved later.
    202          *
    203          * <p>The id of the last selected dataset can be read from
    204          * {@link AutofillService#getFillEventHistory()}. If the id is not set it will not be clear
    205          * if a dataset was selected as {@link AutofillService#getFillEventHistory()} uses
    206          * {@code null} to indicate that no dataset was selected.
    207          *
    208          * @param id id for this dataset or {@code null} to unset.
    209 
    210          * @return This builder.
    211          */
    212         public @NonNull Builder setId(@Nullable String id) {
    213             throwIfDestroyed();
    214 
    215             mId = id;
    216             return this;
    217         }
    218 
    219         /**
    220          * Sets the value of a field.
    221          *
    222          * @param id id returned by {@link
    223          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
    224          * @param value value to be autofilled. Pass {@code null} if you do not have the value
    225          *        but the target view is a logical part of the dataset. For example, if
    226          *        the dataset needs an authentication and you have no access to the value.
    227          * @return This builder.
    228          * @throws IllegalStateException if the builder was constructed without a
    229          * {@link RemoteViews presentation}.
    230          */
    231         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
    232             throwIfDestroyed();
    233             if (mPresentation == null) {
    234                 throw new IllegalStateException("Dataset presentation not set on constructor");
    235             }
    236             setValueAndPresentation(id, value, null);
    237             return this;
    238         }
    239 
    240         /**
    241          * Sets the value of a field, using a custom {@link RemoteViews presentation} to
    242          * visualize it.
    243          *
    244          * @param id id returned by {@link
    245          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
    246          * @param value value to be auto filled. Pass {@code null} if you do not have the value
    247          *        but the target view is a logical part of the dataset. For example, if
    248          *        the dataset needs an authentication and you have no access to the value.
    249          *        Filtering matches any user typed string to {@code null} values.
    250          * @param presentation The presentation used to visualize this field.
    251          * @return This builder.
    252          */
    253         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
    254                 @NonNull RemoteViews presentation) {
    255             throwIfDestroyed();
    256             Preconditions.checkNotNull(presentation, "presentation cannot be null");
    257             setValueAndPresentation(id, value, presentation);
    258             return this;
    259         }
    260 
    261         private void setValueAndPresentation(AutofillId id, AutofillValue value,
    262                 RemoteViews presentation) {
    263             Preconditions.checkNotNull(id, "id cannot be null");
    264             if (mFieldIds != null) {
    265                 final int existingIdx = mFieldIds.indexOf(id);
    266                 if (existingIdx >= 0) {
    267                     mFieldValues.set(existingIdx, value);
    268                     mFieldPresentations.set(existingIdx, presentation);
    269                     return;
    270                 }
    271             } else {
    272                 mFieldIds = new ArrayList<>();
    273                 mFieldValues = new ArrayList<>();
    274                 mFieldPresentations = new ArrayList<>();
    275             }
    276             mFieldIds.add(id);
    277             mFieldValues.add(value);
    278             mFieldPresentations.add(presentation);
    279         }
    280 
    281         /**
    282          * Creates a new {@link Dataset} instance.
    283          *
    284          * <p>You should not interact with this builder once this method is called.
    285          *
    286          * <p>It is required that you specify at least one field before calling this method. It's
    287          * also mandatory to provide a presentation view to visualize the data set in the UI.
    288          *
    289          * @return The built dataset.
    290          */
    291         public @NonNull Dataset build() {
    292             throwIfDestroyed();
    293             mDestroyed = true;
    294             if (mFieldIds == null) {
    295                 throw new IllegalArgumentException("at least one value must be set");
    296             }
    297             return new Dataset(this);
    298         }
    299 
    300         private void throwIfDestroyed() {
    301             if (mDestroyed) {
    302                 throw new IllegalStateException("Already called #build()");
    303             }
    304         }
    305     }
    306 
    307     /////////////////////////////////////
    308     //  Parcelable "contract" methods. //
    309     /////////////////////////////////////
    310 
    311     @Override
    312     public int describeContents() {
    313         return 0;
    314     }
    315 
    316     @Override
    317     public void writeToParcel(Parcel parcel, int flags) {
    318         parcel.writeParcelable(mPresentation, flags);
    319         parcel.writeTypedArrayList(mFieldIds, flags);
    320         parcel.writeTypedArrayList(mFieldValues, flags);
    321         parcel.writeParcelableList(mFieldPresentations, flags);
    322         parcel.writeParcelable(mAuthentication, flags);
    323         parcel.writeString(mId);
    324     }
    325 
    326     public static final Creator<Dataset> CREATOR = new Creator<Dataset>() {
    327         @Override
    328         public Dataset createFromParcel(Parcel parcel) {
    329             // Always go through the builder to ensure the data ingested by
    330             // the system obeys the contract of the builder to avoid attacks
    331             // using specially crafted parcels.
    332             final RemoteViews presentation = parcel.readParcelable(null);
    333             final Builder builder = (presentation == null)
    334                     ? new Builder()
    335                     : new Builder(presentation);
    336             final ArrayList<AutofillId> ids = parcel.readTypedArrayList(null);
    337             final ArrayList<AutofillValue> values = parcel.readTypedArrayList(null);
    338             final ArrayList<RemoteViews> presentations = new ArrayList<>();
    339             parcel.readParcelableList(presentations, null);
    340             final int idCount = (ids != null) ? ids.size() : 0;
    341             final int valueCount = (values != null) ? values.size() : 0;
    342             for (int i = 0; i < idCount; i++) {
    343                 final AutofillId id = ids.get(i);
    344                 final AutofillValue value = (valueCount > i) ? values.get(i) : null;
    345                 final RemoteViews fieldPresentation = presentations.isEmpty() ? null
    346                         : presentations.get(i);
    347                 builder.setValueAndPresentation(id, value, fieldPresentation);
    348             }
    349             builder.setAuthentication(parcel.readParcelable(null));
    350             builder.setId(parcel.readString());
    351             return builder.build();
    352         }
    353 
    354         @Override
    355         public Dataset[] newArray(int size) {
    356             return new Dataset[size];
    357         }
    358     };
    359 }
    360