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