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