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 import java.util.regex.Pattern;
     34 
     35 /**
     36  * A dataset object represents a group of fields (key / value pairs) used to autofill parts of a
     37  * screen.
     38  *
     39  * <a name="BasicUsage"></a>
     40  * <h3>Basic usage</h3>
     41  *
     42  * <p>In its simplest form, a dataset contains one or more fields (comprised of
     43  * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter
     44  * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields
     45  * (each field could have its own {@link RemoteViews presentation}, or use the default
     46  * {@link RemoteViews presentation} associated with the whole dataset).
     47  *
     48  * <p>When an autofill service returns datasets in a {@link FillResponse}
     49  * and the screen input is focused in a view that is present in at least one of these datasets,
     50  * the Android System displays a UI containing the {@link RemoteViews presentation} of
     51  * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
     52  * dataset from the UI, all views in that dataset are autofilled.
     53  *
     54  * <a name="Authentication"></a>
     55  * <h3>Dataset authentication</h3>
     56  *
     57  * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates
     58  * the dataset&mdash;in that case, when a dataset is selected by the user, the Android System
     59  * launches an intent set by the service to "unlock" the dataset.
     60  *
     61  * <p>For example, when a data set contains credit card information (such as number,
     62  * expiration date, and verification code), you could provide a dataset presentation saying
     63  * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking
     64  * the user to enter the credit card code, and if the user enters a valid code, you could then
     65  * "unlock" the dataset.
     66  *
     67  * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example,
     68  * if the activity being autofilled is an account creation screen, you could use an authenticated
     69  * dataset to automatically generate a random password for the user.
     70  *
     71  * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset
     72  * authentication mechanism.
     73  *
     74  * <a name="Filtering"></a>
     75  * <h3>Filtering</h3>
     76  * <p>The autofill UI automatically changes which values are shown based on value of the view
     77  * anchoring it, following the rules below:
     78  * <ol>
     79  *   <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not
     80  * {@link AutofillValue#isText() text} or is empty, all datasets are shown.
     81  *   <li>Datasets that have a filter regex (set through
     82  * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or
     83  * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose
     84  * regex matches the view's text value converted to lower case are shown.
     85  *   <li>Datasets that do not require authentication, have a field value that is
     86  * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts
     87  * with the lower case value of the view's text are shown.
     88  *   <li>All other datasets are hidden.
     89  * </ol>
     90  *
     91  * <a name="MoreInfo"></a>
     92  * <h3>More information</h3>
     93  * <p>See {@link android.service.autofill.AutofillService} for more information and examples about
     94  * the role of datasets in the autofill workflow.
     95  */
     96 public final class Dataset implements Parcelable {
     97 
     98     private final ArrayList<AutofillId> mFieldIds;
     99     private final ArrayList<AutofillValue> mFieldValues;
    100     private final ArrayList<RemoteViews> mFieldPresentations;
    101     private final ArrayList<DatasetFieldFilter> mFieldFilters;
    102     private final RemoteViews mPresentation;
    103     private final IntentSender mAuthentication;
    104     @Nullable String mId;
    105 
    106     private Dataset(Builder builder) {
    107         mFieldIds = builder.mFieldIds;
    108         mFieldValues = builder.mFieldValues;
    109         mFieldPresentations = builder.mFieldPresentations;
    110         mFieldFilters = builder.mFieldFilters;
    111         mPresentation = builder.mPresentation;
    112         mAuthentication = builder.mAuthentication;
    113         mId = builder.mId;
    114     }
    115 
    116     /** @hide */
    117     public @Nullable ArrayList<AutofillId> getFieldIds() {
    118         return mFieldIds;
    119     }
    120 
    121     /** @hide */
    122     public @Nullable ArrayList<AutofillValue> getFieldValues() {
    123         return mFieldValues;
    124     }
    125 
    126     /** @hide */
    127     public RemoteViews getFieldPresentation(int index) {
    128         final RemoteViews customPresentation = mFieldPresentations.get(index);
    129         return customPresentation != null ? customPresentation : mPresentation;
    130     }
    131 
    132     /** @hide */
    133     @Nullable
    134     public DatasetFieldFilter getFilter(int index) {
    135         return mFieldFilters.get(index);
    136     }
    137 
    138     /** @hide */
    139     public @Nullable IntentSender getAuthentication() {
    140         return mAuthentication;
    141     }
    142 
    143     /** @hide */
    144     public boolean isEmpty() {
    145         return mFieldIds == null || mFieldIds.isEmpty();
    146     }
    147 
    148     @Override
    149     public String toString() {
    150         if (!sDebug) return super.toString();
    151 
    152         final StringBuilder builder = new StringBuilder("Dataset[");
    153         if (mId == null) {
    154             builder.append("noId");
    155         } else {
    156             // Cannot disclose id because it could contain PII.
    157             builder.append("id=").append(mId.length()).append("_chars");
    158         }
    159         if (mFieldIds != null) {
    160             builder.append(", fieldIds=").append(mFieldIds);
    161         }
    162         if (mFieldValues != null) {
    163             builder.append(", fieldValues=").append(mFieldValues);
    164         }
    165         if (mFieldPresentations != null) {
    166             builder.append(", fieldPresentations=").append(mFieldPresentations.size());
    167 
    168         }
    169         if (mFieldFilters != null) {
    170             builder.append(", fieldFilters=").append(mFieldFilters.size());
    171         }
    172         if (mPresentation != null) {
    173             builder.append(", hasPresentation");
    174         }
    175         if (mAuthentication != null) {
    176             builder.append(", hasAuthentication");
    177         }
    178         return builder.append(']').toString();
    179     }
    180 
    181     /**
    182      * Gets the id of this dataset.
    183      *
    184      * @return The id of this dataset or {@code null} if not set
    185      *
    186      * @hide
    187      */
    188     public String getId() {
    189         return mId;
    190     }
    191 
    192     /**
    193      * A builder for {@link Dataset} objects. You must provide at least
    194      * one value for a field or set an authentication intent.
    195      */
    196     public static final class Builder {
    197         private ArrayList<AutofillId> mFieldIds;
    198         private ArrayList<AutofillValue> mFieldValues;
    199         private ArrayList<RemoteViews> mFieldPresentations;
    200         private ArrayList<DatasetFieldFilter> mFieldFilters;
    201         private RemoteViews mPresentation;
    202         private IntentSender mAuthentication;
    203         private boolean mDestroyed;
    204         @Nullable private String mId;
    205 
    206         /**
    207          * Creates a new builder.
    208          *
    209          * @param presentation The presentation used to visualize this dataset.
    210          */
    211         public Builder(@NonNull RemoteViews presentation) {
    212             Preconditions.checkNotNull(presentation, "presentation must be non-null");
    213             mPresentation = presentation;
    214         }
    215 
    216         /**
    217          * Creates a new builder for a dataset where each field will be visualized independently.
    218          *
    219          * <p>When using this constructor, fields must be set through
    220          * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
    221          * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
    222          */
    223         public Builder() {
    224         }
    225 
    226         /**
    227          * Triggers a custom UI before before autofilling the screen with the contents of this
    228          * dataset.
    229          *
    230          * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
    231          * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
    232          * for examples.
    233          *
    234          * <p>This method is called when you need to provide an authentication
    235          * UI for the data set. For example, when a data set contains credit card information
    236          * (such as number, expiration date, and verification code), you can display UI
    237          * asking for the verification code before filing in the data. Even if the
    238          * data set is completely populated the system will launch the specified authentication
    239          * intent and will need your approval to fill it in. Since the data set is "locked"
    240          * until the user authenticates it, typically this data set name is masked
    241          * (for example, "VISA....1234"). Typically you would want to store the data set
    242          * labels non-encrypted and the actual sensitive data encrypted and not in memory.
    243          * This allows showing the labels in the UI while involving the user if one of
    244          * the items with these labels is chosen. Note that if you use sensitive data as
    245          * a label, for example an email address, then it should also be encrypted.</p>
    246          *
    247          * <p>When a user triggers autofill, the system launches the provided intent
    248          * whose extras will have the {@link
    249          * android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen content},
    250          * and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE client
    251          * state}. Once you complete your authentication flow you should set the activity
    252          * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
    253          * {@link Dataset dataset} or a fully-populated {@link FillResponse response} by
    254          * setting it to the {@link
    255          * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you
    256          * provide a dataset in the result, it will replace the authenticated dataset and
    257          * will be immediately filled in. If you provide a response, it will replace the
    258          * current response and the UI will be refreshed. For example, if you provided
    259          * credit card information without the CVV for the data set in the {@link FillResponse
    260          * response} then the returned data set should contain the CVV entry.
    261          *
    262          * <p><b>Note:</b> Do not make the provided pending intent
    263          * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
    264          * platform needs to fill in the authentication arguments.
    265          *
    266          * @param authentication Intent to an activity with your authentication flow.
    267          * @return this builder.
    268          *
    269          * @see android.app.PendingIntent
    270          */
    271         public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) {
    272             throwIfDestroyed();
    273             mAuthentication = authentication;
    274             return this;
    275         }
    276 
    277         /**
    278          * Sets the id for the dataset so its usage can be tracked.
    279          *
    280          * <p>Dataset usage can be tracked for 2 purposes:
    281          *
    282          * <ul>
    283          *   <li>For statistical purposes, the service can call
    284          * {@link AutofillService#getFillEventHistory()} when handling {@link
    285          * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
    286          * calls.
    287          *   <li>For normal autofill workflow, the service can call
    288          *   {@link SaveRequest#getDatasetIds()} when handling
    289          *   {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} calls.
    290          * </ul>
    291          *
    292          * @param id id for this dataset or {@code null} to unset.
    293          *
    294          * @return this builder.
    295          */
    296         public @NonNull Builder setId(@Nullable String id) {
    297             throwIfDestroyed();
    298             mId = id;
    299             return this;
    300         }
    301 
    302         /**
    303          * Sets the value of a field.
    304          *
    305          * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would
    306          * throw an {@link IllegalStateException} if this builder was constructed without a
    307          * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and
    308          * higher removed this restriction because datasets used as an
    309          * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT
    310          * authentication result} do not need a presentation. But if you don't set the presentation
    311          * in the constructor in a dataset that is meant to be shown to the user, the autofill UI
    312          * for this field will not be displayed.
    313          *
    314          * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and
    315          * higher, datasets that require authentication can be also be filtered by passing a
    316          * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter.
    317          *
    318          * @param id id returned by {@link
    319          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
    320          * @param value value to be autofilled. Pass {@code null} if you do not have the value
    321          *        but the target view is a logical part of the dataset. For example, if
    322          *        the dataset needs authentication and you have no access to the value.
    323          * @return this builder.
    324          */
    325         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
    326             throwIfDestroyed();
    327             setLifeTheUniverseAndEverything(id, value, null, null);
    328             return this;
    329         }
    330 
    331         /**
    332          * Sets the value of a field, using a custom {@link RemoteViews presentation} to
    333          * visualize it.
    334          *
    335          * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and
    336          * higher, datasets that require authentication can be also be filtered by passing a
    337          * {@link AutofillValue#forText(CharSequence) text value} as the  {@code value} parameter.
    338          *
    339          * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
    340          * or background color: Autofill on different platforms may have different themes.
    341          *
    342          * @param id id returned by {@link
    343          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
    344          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
    345          *        but the target view is a logical part of the dataset. For example, if
    346          *        the dataset needs authentication and you have no access to the value.
    347          * @param presentation the presentation used to visualize this field.
    348          * @return this builder.
    349          *
    350          */
    351         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
    352                 @NonNull RemoteViews presentation) {
    353             throwIfDestroyed();
    354             Preconditions.checkNotNull(presentation, "presentation cannot be null");
    355             setLifeTheUniverseAndEverything(id, value, presentation, null);
    356             return this;
    357         }
    358 
    359         /**
    360          * Sets the value of a field using an <a href="#Filtering">explicit filter</a>.
    361          *
    362          * <p>This method is typically used when the dataset requires authentication and the service
    363          * does not know its value but wants to hide the dataset after the user enters a minimum
    364          * number of characters. For example, if the dataset represents a credit card number and the
    365          * service does not want to show the "Tap to authenticate" message until the user tapped
    366          * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
    367          *
    368          * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
    369          * value it's easier to filter by calling {@link #setValue(AutofillId, AutofillValue)} and
    370          * use the value to filter.
    371          *
    372          * @param id id returned by {@link
    373          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
    374          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
    375          *        but the target view is a logical part of the dataset. For example, if
    376          *        the dataset needs authentication and you have no access to the value.
    377          * @param filter regex used to determine if the dataset should be shown in the autofill UI;
    378          *        when {@code null}, it disables filtering on that dataset (this is the recommended
    379          *        approach when {@code value} is not {@code null} and field contains sensitive data
    380          *        such as passwords).
    381          *
    382          * @return this builder.
    383          * @throws IllegalStateException if the builder was constructed without a
    384          *         {@link RemoteViews presentation}.
    385          */
    386         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
    387                 @Nullable Pattern filter) {
    388             throwIfDestroyed();
    389             Preconditions.checkState(mPresentation != null,
    390                     "Dataset presentation not set on constructor");
    391             setLifeTheUniverseAndEverything(id, value, null, new DatasetFieldFilter(filter));
    392             return this;
    393         }
    394 
    395         /**
    396          * Sets the value of a field, using a custom {@link RemoteViews presentation} to
    397          * visualize it and a <a href="#Filtering">explicit filter</a>.
    398          *
    399          * <p>This method is typically used when the dataset requires authentication and the service
    400          * does not know its value but wants to hide the dataset after the user enters a minimum
    401          * number of characters. For example, if the dataset represents a credit card number and the
    402          * service does not want to show the "Tap to authenticate" message until the user tapped
    403          * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
    404          *
    405          * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
    406          * value it's easier to filter by calling
    407          * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
    408          *
    409          * @param id id returned by {@link
    410          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
    411          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
    412          *        but the target view is a logical part of the dataset. For example, if
    413          *        the dataset needs authentication and you have no access to the value.
    414          * @param filter regex used to determine if the dataset should be shown in the autofill UI;
    415          *        when {@code null}, it disables filtering on that dataset (this is the recommended
    416          *        approach when {@code value} is not {@code null} and field contains sensitive data
    417          *        such as passwords).
    418          * @param presentation the presentation used to visualize this field.
    419          *
    420          * @return this builder.
    421          */
    422         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
    423                 @Nullable Pattern filter, @NonNull RemoteViews presentation) {
    424             throwIfDestroyed();
    425             Preconditions.checkNotNull(presentation, "presentation cannot be null");
    426             setLifeTheUniverseAndEverything(id, value, presentation,
    427                     new DatasetFieldFilter(filter));
    428             return this;
    429         }
    430 
    431         private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
    432                 @Nullable AutofillValue value, @Nullable RemoteViews presentation,
    433                 @Nullable DatasetFieldFilter filter) {
    434             Preconditions.checkNotNull(id, "id cannot be null");
    435             if (mFieldIds != null) {
    436                 final int existingIdx = mFieldIds.indexOf(id);
    437                 if (existingIdx >= 0) {
    438                     mFieldValues.set(existingIdx, value);
    439                     mFieldPresentations.set(existingIdx, presentation);
    440                     mFieldFilters.set(existingIdx, filter);
    441                     return;
    442                 }
    443             } else {
    444                 mFieldIds = new ArrayList<>();
    445                 mFieldValues = new ArrayList<>();
    446                 mFieldPresentations = new ArrayList<>();
    447                 mFieldFilters = new ArrayList<>();
    448             }
    449             mFieldIds.add(id);
    450             mFieldValues.add(value);
    451             mFieldPresentations.add(presentation);
    452             mFieldFilters.add(filter);
    453         }
    454 
    455         /**
    456          * Creates a new {@link Dataset} instance.
    457          *
    458          * <p>You should not interact with this builder once this method is called.
    459          *
    460          * @throws IllegalStateException if no field was set (through
    461          * {@link #setValue(AutofillId, AutofillValue)} or
    462          * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}).
    463          *
    464          * @return The built dataset.
    465          */
    466         public @NonNull Dataset build() {
    467             throwIfDestroyed();
    468             mDestroyed = true;
    469             if (mFieldIds == null) {
    470                 throw new IllegalStateException("at least one value must be set");
    471             }
    472             return new Dataset(this);
    473         }
    474 
    475         private void throwIfDestroyed() {
    476             if (mDestroyed) {
    477                 throw new IllegalStateException("Already called #build()");
    478             }
    479         }
    480     }
    481 
    482     /////////////////////////////////////
    483     //  Parcelable "contract" methods. //
    484     /////////////////////////////////////
    485 
    486     @Override
    487     public int describeContents() {
    488         return 0;
    489     }
    490 
    491     @Override
    492     public void writeToParcel(Parcel parcel, int flags) {
    493         parcel.writeParcelable(mPresentation, flags);
    494         parcel.writeTypedList(mFieldIds, flags);
    495         parcel.writeTypedList(mFieldValues, flags);
    496         parcel.writeTypedList(mFieldPresentations, flags);
    497         parcel.writeTypedList(mFieldFilters, flags);
    498         parcel.writeParcelable(mAuthentication, flags);
    499         parcel.writeString(mId);
    500     }
    501 
    502     public static final Creator<Dataset> CREATOR = new Creator<Dataset>() {
    503         @Override
    504         public Dataset createFromParcel(Parcel parcel) {
    505             // Always go through the builder to ensure the data ingested by
    506             // the system obeys the contract of the builder to avoid attacks
    507             // using specially crafted parcels.
    508             final RemoteViews presentation = parcel.readParcelable(null);
    509             final Builder builder = (presentation == null)
    510                     ? new Builder()
    511                     : new Builder(presentation);
    512             final ArrayList<AutofillId> ids =
    513                     parcel.createTypedArrayList(AutofillId.CREATOR);
    514             final ArrayList<AutofillValue> values =
    515                     parcel.createTypedArrayList(AutofillValue.CREATOR);
    516             final ArrayList<RemoteViews> presentations =
    517                     parcel.createTypedArrayList(RemoteViews.CREATOR);
    518             final ArrayList<DatasetFieldFilter> filters =
    519                     parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
    520             for (int i = 0; i < ids.size(); i++) {
    521                 final AutofillId id = ids.get(i);
    522                 final AutofillValue value = values.get(i);
    523                 final RemoteViews fieldPresentation = presentations.get(i);
    524                 final DatasetFieldFilter filter = filters.get(i);
    525                 builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter);
    526             }
    527             builder.setAuthentication(parcel.readParcelable(null));
    528             builder.setId(parcel.readString());
    529             return builder.build();
    530         }
    531 
    532         @Override
    533         public Dataset[] newArray(int size) {
    534             return new Dataset[size];
    535         }
    536     };
    537 
    538     /**
    539      * Helper class used to indicate when the service explicitly set a {@link Pattern} filter for a
    540      * dataset field&dash; we cannot use a {@link Pattern} directly because then we wouldn't be
    541      * able to differentiate whether the service explicitly passed a {@code null} filter to disable
    542      * filter, or when it called the methods that does not take a filter {@link Pattern}.
    543      *
    544      * @hide
    545      */
    546     public static final class DatasetFieldFilter implements Parcelable {
    547 
    548         @Nullable
    549         public final Pattern pattern;
    550 
    551         private DatasetFieldFilter(@Nullable Pattern pattern) {
    552             this.pattern = pattern;
    553         }
    554 
    555         @Override
    556         public String toString() {
    557             if (!sDebug) return super.toString();
    558 
    559             // Cannot log pattern because it could contain PII
    560             return pattern == null ? "null" : pattern.pattern().length() + "_chars";
    561         }
    562 
    563         @Override
    564         public int describeContents() {
    565             return 0;
    566         }
    567 
    568         @Override
    569         public void writeToParcel(Parcel parcel, int flags) {
    570             parcel.writeSerializable(pattern);
    571         }
    572 
    573         @SuppressWarnings("hiding")
    574         public static final Creator<DatasetFieldFilter> CREATOR =
    575                 new Creator<DatasetFieldFilter>() {
    576 
    577             @Override
    578             public DatasetFieldFilter createFromParcel(Parcel parcel) {
    579                 return new DatasetFieldFilter((Pattern) parcel.readSerializable());
    580             }
    581 
    582             @Override
    583             public DatasetFieldFilter[] newArray(int size) {
    584                 return new DatasetFieldFilter[size];
    585             }
    586         };
    587     }
    588 }
    589