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