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