Dataset.java revision da9ea34c029df809e6b833a483408662e13ca9a1
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
17package android.service.autofill;
18
19import static android.view.autofill.Helper.sDebug;
20
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.content.IntentSender;
24import android.os.Parcel;
25import android.os.Parcelable;
26import android.view.autofill.AutofillId;
27import android.view.autofill.AutofillValue;
28import android.widget.RemoteViews;
29
30import com.android.internal.util.Preconditions;
31
32import java.util.ArrayList;
33import 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 */
96public 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         * @param id id returned by {@link
340         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
341         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
342         *        but the target view is a logical part of the dataset. For example, if
343         *        the dataset needs authentication and you have no access to the value.
344         * @param presentation the presentation used to visualize this field.
345         * @return this builder.
346         *
347         */
348        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
349                @NonNull RemoteViews presentation) {
350            throwIfDestroyed();
351            Preconditions.checkNotNull(presentation, "presentation cannot be null");
352            setLifeTheUniverseAndEverything(id, value, presentation, null);
353            return this;
354        }
355
356        /**
357         * Sets the value of a field using an <a href="#Filtering">explicit filter</a>.
358         *
359         * <p>This method is typically used when the dataset requires authentication and the service
360         * does not know its value but wants to hide the dataset after the user enters a minimum
361         * number of characters. For example, if the dataset represents a credit card number and the
362         * service does not want to show the "Tap to authenticate" message until the user tapped
363         * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
364         *
365         * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
366         * value it's easier to filter by calling {@link #setValue(AutofillId, AutofillValue)} and
367         * use the value to filter.
368         *
369         * @param id id returned by {@link
370         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
371         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
372         *        but the target view is a logical part of the dataset. For example, if
373         *        the dataset needs authentication and you have no access to the value.
374         * @param filter regex used to determine if the dataset should be shown in the autofill UI;
375         *        when {@code null}, it disables filtering on that dataset (this is the recommended
376         *        approach when {@code value} is not {@code null} and field contains sensitive data
377         *        such as passwords).
378         *
379         * @return this builder.
380         * @throws IllegalStateException if the builder was constructed without a
381         *         {@link RemoteViews presentation}.
382         */
383        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
384                @Nullable Pattern filter) {
385            throwIfDestroyed();
386            Preconditions.checkState(mPresentation != null,
387                    "Dataset presentation not set on constructor");
388            setLifeTheUniverseAndEverything(id, value, null, new DatasetFieldFilter(filter));
389            return this;
390        }
391
392        /**
393         * Sets the value of a field, using a custom {@link RemoteViews presentation} to
394         * visualize it and a <a href="#Filtering">explicit filter</a>.
395         *
396         * <p>This method is typically used when the dataset requires authentication and the service
397         * does not know its value but wants to hide the dataset after the user enters a minimum
398         * number of characters. For example, if the dataset represents a credit card number and the
399         * service does not want to show the "Tap to authenticate" message until the user tapped
400         * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
401         *
402         * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
403         * value it's easier to filter by calling
404         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
405         *
406         * @param id id returned by {@link
407         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
408         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
409         *        but the target view is a logical part of the dataset. For example, if
410         *        the dataset needs authentication and you have no access to the value.
411         * @param filter regex used to determine if the dataset should be shown in the autofill UI;
412         *        when {@code null}, it disables filtering on that dataset (this is the recommended
413         *        approach when {@code value} is not {@code null} and field contains sensitive data
414         *        such as passwords).
415         * @param presentation the presentation used to visualize this field.
416         *
417         * @return this builder.
418         */
419        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
420                @Nullable Pattern filter, @NonNull RemoteViews presentation) {
421            throwIfDestroyed();
422            Preconditions.checkNotNull(presentation, "presentation cannot be null");
423            setLifeTheUniverseAndEverything(id, value, presentation,
424                    new DatasetFieldFilter(filter));
425            return this;
426        }
427
428        private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
429                @Nullable AutofillValue value, @Nullable RemoteViews presentation,
430                @Nullable DatasetFieldFilter filter) {
431            Preconditions.checkNotNull(id, "id cannot be null");
432            if (mFieldIds != null) {
433                final int existingIdx = mFieldIds.indexOf(id);
434                if (existingIdx >= 0) {
435                    mFieldValues.set(existingIdx, value);
436                    mFieldPresentations.set(existingIdx, presentation);
437                    mFieldFilters.set(existingIdx, filter);
438                    return;
439                }
440            } else {
441                mFieldIds = new ArrayList<>();
442                mFieldValues = new ArrayList<>();
443                mFieldPresentations = new ArrayList<>();
444                mFieldFilters = new ArrayList<>();
445            }
446            mFieldIds.add(id);
447            mFieldValues.add(value);
448            mFieldPresentations.add(presentation);
449            mFieldFilters.add(filter);
450        }
451
452        /**
453         * Creates a new {@link Dataset} instance.
454         *
455         * <p>You should not interact with this builder once this method is called.
456         *
457         * @throws IllegalStateException if no field was set (through
458         * {@link #setValue(AutofillId, AutofillValue)} or
459         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}).
460         *
461         * @return The built dataset.
462         */
463        public @NonNull Dataset build() {
464            throwIfDestroyed();
465            mDestroyed = true;
466            if (mFieldIds == null) {
467                throw new IllegalStateException("at least one value must be set");
468            }
469            return new Dataset(this);
470        }
471
472        private void throwIfDestroyed() {
473            if (mDestroyed) {
474                throw new IllegalStateException("Already called #build()");
475            }
476        }
477    }
478
479    /////////////////////////////////////
480    //  Parcelable "contract" methods. //
481    /////////////////////////////////////
482
483    @Override
484    public int describeContents() {
485        return 0;
486    }
487
488    @Override
489    public void writeToParcel(Parcel parcel, int flags) {
490        parcel.writeParcelable(mPresentation, flags);
491        parcel.writeTypedList(mFieldIds, flags);
492        parcel.writeTypedList(mFieldValues, flags);
493        parcel.writeTypedList(mFieldPresentations, flags);
494        parcel.writeTypedList(mFieldFilters, flags);
495        parcel.writeParcelable(mAuthentication, flags);
496        parcel.writeString(mId);
497    }
498
499    public static final Creator<Dataset> CREATOR = new Creator<Dataset>() {
500        @Override
501        public Dataset createFromParcel(Parcel parcel) {
502            // Always go through the builder to ensure the data ingested by
503            // the system obeys the contract of the builder to avoid attacks
504            // using specially crafted parcels.
505            final RemoteViews presentation = parcel.readParcelable(null);
506            final Builder builder = (presentation == null)
507                    ? new Builder()
508                    : new Builder(presentation);
509            final ArrayList<AutofillId> ids =
510                    parcel.createTypedArrayList(AutofillId.CREATOR);
511            final ArrayList<AutofillValue> values =
512                    parcel.createTypedArrayList(AutofillValue.CREATOR);
513            final ArrayList<RemoteViews> presentations =
514                    parcel.createTypedArrayList(RemoteViews.CREATOR);
515            final ArrayList<DatasetFieldFilter> filters =
516                    parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
517            for (int i = 0; i < ids.size(); i++) {
518                final AutofillId id = ids.get(i);
519                final AutofillValue value = values.get(i);
520                final RemoteViews fieldPresentation = presentations.get(i);
521                final DatasetFieldFilter filter = filters.get(i);
522                builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter);
523            }
524            builder.setAuthentication(parcel.readParcelable(null));
525            builder.setId(parcel.readString());
526            return builder.build();
527        }
528
529        @Override
530        public Dataset[] newArray(int size) {
531            return new Dataset[size];
532        }
533    };
534
535    /**
536     * Helper class used to indicate when the service explicitly set a {@link Pattern} filter for a
537     * dataset field&dash; we cannot use a {@link Pattern} directly because then we wouldn't be
538     * able to differentiate whether the service explicitly passed a {@code null} filter to disable
539     * filter, or when it called the methods that does not take a filter {@link Pattern}.
540     *
541     * @hide
542     */
543    public static final class DatasetFieldFilter implements Parcelable {
544
545        @Nullable
546        public final Pattern pattern;
547
548        private DatasetFieldFilter(@Nullable Pattern pattern) {
549            this.pattern = pattern;
550        }
551
552        @Override
553        public String toString() {
554            if (!sDebug) return super.toString();
555
556            // Cannot log pattern because it could contain PII
557            return pattern == null ? "null" : pattern.pattern().length() + "_chars";
558        }
559
560        @Override
561        public int describeContents() {
562            return 0;
563        }
564
565        @Override
566        public void writeToParcel(Parcel parcel, int flags) {
567            parcel.writeSerializable(pattern);
568        }
569
570        @SuppressWarnings("hiding")
571        public static final Creator<DatasetFieldFilter> CREATOR =
572                new Creator<DatasetFieldFilter>() {
573
574            @Override
575            public DatasetFieldFilter createFromParcel(Parcel parcel) {
576                return new DatasetFieldFilter((Pattern) parcel.readSerializable());
577            }
578
579            @Override
580            public DatasetFieldFilter[] newArray(int size) {
581                return new DatasetFieldFilter[size];
582            }
583        };
584    }
585}
586