2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.service.autofill;
19 import static android.view.autofill.Helper.sDebug;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.IntentSender;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.view.autofill.AutofillId;
27 import android.view.autofill.AutofillValue;
28 import android.widget.RemoteViews;
30 import com.android.internal.util.Preconditions;
32 import java.util.ArrayList;
35 * A dataset object represents a group of key/value pairs used to autofill parts of a screen.
37 * <p>In its simplest form, a dataset contains one or more key / value pairs (comprised of
38 * {@link AutofillId} and {@link AutofillValue} respectively); and one or more
39 * {@link RemoteViews presentation} for these pairs (a pair could have its own
40 * {@link RemoteViews presentation}, or use the default {@link RemoteViews presentation} associated
41 * with the whole dataset). When an autofill service returns datasets in a {@link FillResponse}
42 * and the screen input is focused in a view that is present in at least one of these datasets,
43 * the Android System displays a UI affordance containing the {@link RemoteViews presentation} of
44 * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
45 * dataset from the affordance, all views in that dataset are autofilled.
47 * <p>In a more sophisticated form, the dataset value can be protected until the user authenticates
48 * the dataset - see {@link Dataset.Builder#setAuthentication(IntentSender)}.
50 * @see android.service.autofill.AutofillService for more information and examples about the
51 * role of datasets in the autofill workflow.
53 public final class Dataset implements Parcelable {
55 private final ArrayList<AutofillId> mFieldIds;
56 private final ArrayList<AutofillValue> mFieldValues;
57 private final ArrayList<RemoteViews> mFieldPresentations;
58 private final RemoteViews mPresentation;
59 private final IntentSender mAuthentication;
62 private Dataset(Builder builder) {
63 mFieldIds = builder.mFieldIds;
64 mFieldValues = builder.mFieldValues;
65 mFieldPresentations = builder.mFieldPresentations;
66 mPresentation = builder.mPresentation;
67 mAuthentication = builder.mAuthentication;
72 public @Nullable ArrayList<AutofillId> getFieldIds() {
77 public @Nullable ArrayList<AutofillValue> getFieldValues() {
82 public RemoteViews getFieldPresentation(int index) {
83 final RemoteViews customPresentation = mFieldPresentations.get(index);
84 return customPresentation != null ? customPresentation : mPresentation;
88 public @Nullable IntentSender getAuthentication() {
89 return mAuthentication;
93 public boolean isEmpty() {
94 return mFieldIds == null || mFieldIds.isEmpty();
98 public String toString() {
99 if (!sDebug) return super.toString();
101 return new StringBuilder("Dataset " + mId + " [")
102 .append("fieldIds=").append(mFieldIds)
103 .append(", fieldValues=").append(mFieldValues)
104 .append(", fieldPresentations=")
105 .append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
106 .append(", hasPresentation=").append(mPresentation != null)
107 .append(", hasAuthentication=").append(mAuthentication != null)
108 .append(']').toString();
112 * Gets the id of this dataset.
114 * @return The id of this dataset or {@code null} if not set
118 public String getId() {
123 * A builder for {@link Dataset} objects. You must provide at least
124 * one value for a field or set an authentication intent.
126 public static final class Builder {
127 private ArrayList<AutofillId> mFieldIds;
128 private ArrayList<AutofillValue> mFieldValues;
129 private ArrayList<RemoteViews> mFieldPresentations;
130 private RemoteViews mPresentation;
131 private IntentSender mAuthentication;
132 private boolean mDestroyed;
133 @Nullable private String mId;
136 * Creates a new builder.
138 * @param presentation The presentation used to visualize this dataset.
140 public Builder(@NonNull RemoteViews presentation) {
141 Preconditions.checkNotNull(presentation, "presentation must be non-null");
142 mPresentation = presentation;
146 * Creates a new builder for a dataset where each field will be visualized independently.
148 * <p>When using this constructor, fields must be set through
149 * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}.
155 * Requires a dataset authentication before autofilling the activity with this dataset.
157 * <p>This method is called when you need to provide an authentication
158 * UI for the data set. For example, when a data set contains credit card information
159 * (such as number, expiration date, and verification code), you can display UI
160 * asking for the verification code before filing in the data. Even if the
161 * data set is completely populated the system will launch the specified authentication
162 * intent and will need your approval to fill it in. Since the data set is "locked"
163 * until the user authenticates it, typically this data set name is masked
164 * (for example, "VISA....1234"). Typically you would want to store the data set
165 * labels non-encrypted and the actual sensitive data encrypted and not in memory.
166 * This allows showing the labels in the UI while involving the user if one of
167 * the items with these labels is chosen. Note that if you use sensitive data as
168 * a label, for example an email address, then it should also be encrypted.</p>
170 * <p>When a user triggers autofill, the system launches the provided intent
171 * whose extras will have the {@link
172 * android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen content},
173 * and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE client
174 * state}. Once you complete your authentication flow you should set the activity
175 * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
176 * {@link Dataset dataset} or a fully-populated {@link FillResponse response} by
177 * setting it to the {@link
178 * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you
179 * provide a dataset in the result, it will replace the authenticated dataset and
180 * will be immediately filled in. If you provide a response, it will replace the
181 * current response and the UI will be refreshed. For example, if you provided
182 * credit card information without the CVV for the data set in the {@link FillResponse
183 * response} then the returned data set should contain the CVV entry.
185 * <p><b>NOTE:</b> Do not make the provided pending intent
186 * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
187 * platform needs to fill in the authentication arguments.
189 * @param authentication Intent to an activity with your authentication flow.
190 * @return This builder.
192 * @see android.app.PendingIntent
194 public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) {
196 mAuthentication = authentication;
201 * Sets the id for the dataset so its usage history can be retrieved later.
203 * <p>The id of the last selected dataset can be read from
204 * {@link AutofillService#getFillEventHistory()}. If the id is not set it will not be clear
205 * if a dataset was selected as {@link AutofillService#getFillEventHistory()} uses
206 * {@code null} to indicate that no dataset was selected.
208 * @param id id for this dataset or {@code null} to unset.
210 * @return This builder.
212 public @NonNull Builder setId(@Nullable String id) {
220 * Sets the value of a field.
222 * @param id id returned by {@link
223 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
224 * @param value value to be autofilled. Pass {@code null} if you do not have the value
225 * but the target view is a logical part of the dataset. For example, if
226 * the dataset needs an authentication and you have no access to the value.
227 * @return This builder.
228 * @throws IllegalStateException if the builder was constructed without a
229 * {@link RemoteViews presentation}.
231 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
233 if (mPresentation == null) {
234 throw new IllegalStateException("Dataset presentation not set on constructor");
236 setValueAndPresentation(id, value, null);
241 * Sets the value of a field, using a custom {@link RemoteViews presentation} to
244 * @param id id returned by {@link
245 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
246 * @param value value to be auto filled. Pass {@code null} if you do not have the value
247 * but the target view is a logical part of the dataset. For example, if
248 * the dataset needs an authentication and you have no access to the value.
249 * Filtering matches any user typed string to {@code null} values.
250 * @param presentation The presentation used to visualize this field.
251 * @return This builder.
253 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
254 @NonNull RemoteViews presentation) {
256 Preconditions.checkNotNull(presentation, "presentation cannot be null");
257 setValueAndPresentation(id, value, presentation);
261 private void setValueAndPresentation(AutofillId id, AutofillValue value,
262 RemoteViews presentation) {
263 Preconditions.checkNotNull(id, "id cannot be null");
264 if (mFieldIds != null) {
265 final int existingIdx = mFieldIds.indexOf(id);
266 if (existingIdx >= 0) {
267 mFieldValues.set(existingIdx, value);
268 mFieldPresentations.set(existingIdx, presentation);
272 mFieldIds = new ArrayList<>();
273 mFieldValues = new ArrayList<>();
274 mFieldPresentations = new ArrayList<>();
277 mFieldValues.add(value);
278 mFieldPresentations.add(presentation);
282 * Creates a new {@link Dataset} instance.
284 * <p>You should not interact with this builder once this method is called.
286 * <p>It is required that you specify at least one field before calling this method. It's
287 * also mandatory to provide a presentation view to visualize the data set in the UI.
289 * @return The built dataset.
291 public @NonNull Dataset build() {
294 if (mFieldIds == null) {
295 throw new IllegalArgumentException("at least one value must be set");
297 return new Dataset(this);
300 private void throwIfDestroyed() {
302 throw new IllegalStateException("Already called #build()");
307 /////////////////////////////////////
308 // Parcelable "contract" methods. //
309 /////////////////////////////////////
312 public int describeContents() {
317 public void writeToParcel(Parcel parcel, int flags) {
318 parcel.writeParcelable(mPresentation, flags);
319 parcel.writeTypedList(mFieldIds, flags);
320 parcel.writeTypedList(mFieldValues, flags);
321 parcel.writeParcelableList(mFieldPresentations, flags);
322 parcel.writeParcelable(mAuthentication, flags);
323 parcel.writeString(mId);
326 public static final Creator<Dataset> CREATOR = new Creator<Dataset>() {
328 public Dataset createFromParcel(Parcel parcel) {
329 // Always go through the builder to ensure the data ingested by
330 // the system obeys the contract of the builder to avoid attacks
331 // using specially crafted parcels.
332 final RemoteViews presentation = parcel.readParcelable(null);
333 final Builder builder = (presentation == null)
335 : new Builder(presentation);
336 final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
337 final ArrayList<AutofillValue> values =
338 parcel.createTypedArrayList(AutofillValue.CREATOR);
339 final ArrayList<RemoteViews> presentations = new ArrayList<>();
340 parcel.readParcelableList(presentations, null);
341 final int idCount = (ids != null) ? ids.size() : 0;
342 final int valueCount = (values != null) ? values.size() : 0;
343 for (int i = 0; i < idCount; i++) {
344 final AutofillId id = ids.get(i);
345 final AutofillValue value = (valueCount > i) ? values.get(i) : null;
346 final RemoteViews fieldPresentation = presentations.isEmpty() ? null
347 : presentations.get(i);
348 builder.setValueAndPresentation(id, value, fieldPresentation);
350 builder.setAuthentication(parcel.readParcelable(null));
351 builder.setId(parcel.readString());
352 return builder.build();
356 public Dataset[] newArray(int size) {
357 return new Dataset[size];