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