blob: d53e62a4fc9093c6e5bd52eb5eeef9711e439b34 [file] [log] [blame]
Felipe Leme6d553872016-12-08 17:13:25 -08001/*
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
Svet Ganov782043c2017-02-11 00:52:02 +000017package android.service.autofill;
Felipe Leme6d553872016-12-08 17:13:25 -080018
Felipe Leme9f9ee252017-04-27 13:56:22 -070019import static android.view.autofill.Helper.sDebug;
Felipe Lemed633f072017-02-14 10:17:17 -080020
Svet Ganov0f4928f2017-02-02 20:02:51 -080021import android.annotation.NonNull;
Felipe Leme436ab6a2016-12-15 11:56:15 -080022import android.annotation.Nullable;
Svet Ganov0f4928f2017-02-02 20:02:51 -080023import android.content.IntentSender;
Felipe Leme6d553872016-12-08 17:13:25 -080024import android.os.Parcel;
25import android.os.Parcelable;
Felipe Leme640f30a2017-03-06 15:44:06 -080026import android.view.autofill.AutofillId;
27import android.view.autofill.AutofillValue;
Svet Ganov00c771dc2017-02-19 00:06:22 -080028import android.widget.RemoteViews;
Felipe Lemed0b18d62017-07-24 17:08:26 -070029
Felipe Leme6d553872016-12-08 17:13:25 -080030import com.android.internal.util.Preconditions;
31
32import java.util.ArrayList;
Felipe Leme27a026a2017-10-02 17:27:49 -070033import java.util.regex.Pattern;
Felipe Leme6d553872016-12-08 17:13:25 -080034
35/**
Laura Davis43e75d92018-08-10 15:46:06 -070036 * <p>A <code>Dataset</code> object represents a group of fields (key / value pairs) used
37 * to autofill parts of a screen.
38 *
39 * <p>For more information about the role of datasets in the autofill workflow, read
40 * <a href="/guide/topics/text/autofill-services">Build autofill services</a> and the
41 * <code><a href="/reference/android/service/autofill/AutofillService">AutofillService</a></code>
42 * documentation.
Felipe Leme6d553872016-12-08 17:13:25 -080043 *
Felipe Leme27a026a2017-10-02 17:27:49 -070044 * <a name="BasicUsage"></a>
45 * <h3>Basic usage</h3>
46 *
47 * <p>In its simplest form, a dataset contains one or more fields (comprised of
48 * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter
49 * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields
50 * (each field could have its own {@link RemoteViews presentation}, or use the default
51 * {@link RemoteViews presentation} associated with the whole dataset).
52 *
53 * <p>When an autofill service returns datasets in a {@link FillResponse}
Felipe Leme2ef19c12017-06-05 11:32:32 -070054 * and the screen input is focused in a view that is present in at least one of these datasets,
Felipe Leme27a026a2017-10-02 17:27:49 -070055 * the Android System displays a UI containing the {@link RemoteViews presentation} of
Felipe Leme2ef19c12017-06-05 11:32:32 -070056 * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
Felipe Leme27a026a2017-10-02 17:27:49 -070057 * dataset from the UI, all views in that dataset are autofilled.
Felipe Leme6d553872016-12-08 17:13:25 -080058 *
Felipe Leme27a026a2017-10-02 17:27:49 -070059 * <a name="Authentication"></a>
60 * <h3>Dataset authentication</h3>
Felipe Leme6d553872016-12-08 17:13:25 -080061 *
Felipe Leme27a026a2017-10-02 17:27:49 -070062 * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates
63 * the dataset&mdash;in that case, when a dataset is selected by the user, the Android System
64 * launches an intent set by the service to "unlock" the dataset.
65 *
66 * <p>For example, when a data set contains credit card information (such as number,
67 * expiration date, and verification code), you could provide a dataset presentation saying
68 * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking
69 * the user to enter the credit card code, and if the user enters a valid code, you could then
70 * "unlock" the dataset.
71 *
72 * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example,
73 * if the activity being autofilled is an account creation screen, you could use an authenticated
74 * dataset to automatically generate a random password for the user.
75 *
76 * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset
77 * authentication mechanism.
78 *
79 * <a name="Filtering"></a>
80 * <h3>Filtering</h3>
81 * <p>The autofill UI automatically changes which values are shown based on value of the view
82 * anchoring it, following the rules below:
83 * <ol>
84 * <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not
85 * {@link AutofillValue#isText() text} or is empty, all datasets are shown.
86 * <li>Datasets that have a filter regex (set through
87 * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or
88 * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose
89 * regex matches the view's text value converted to lower case are shown.
90 * <li>Datasets that do not require authentication, have a field value that is
91 * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts
92 * with the lower case value of the view's text are shown.
93 * <li>All other datasets are hidden.
94 * </ol>
95 *
Felipe Leme6d553872016-12-08 17:13:25 -080096 */
97public final class Dataset implements Parcelable {
Svet Ganov782043c2017-02-11 00:52:02 +000098
Felipe Leme640f30a2017-03-06 15:44:06 -080099 private final ArrayList<AutofillId> mFieldIds;
100 private final ArrayList<AutofillValue> mFieldValues;
Felipe Leme53112062017-03-20 13:24:10 -0700101 private final ArrayList<RemoteViews> mFieldPresentations;
Felipe Leme09d58a42018-01-30 14:04:03 -0800102 private final ArrayList<DatasetFieldFilter> mFieldFilters;
Svet Ganov00c771dc2017-02-19 00:06:22 -0800103 private final RemoteViews mPresentation;
Svet Ganov0f4928f2017-02-02 20:02:51 -0800104 private final IntentSender mAuthentication;
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700105 @Nullable String mId;
Felipe Leme6d553872016-12-08 17:13:25 -0800106
Svet Ganov0f4928f2017-02-02 20:02:51 -0800107 private Dataset(Builder builder) {
Svet Ganov0f4928f2017-02-02 20:02:51 -0800108 mFieldIds = builder.mFieldIds;
109 mFieldValues = builder.mFieldValues;
Felipe Leme53112062017-03-20 13:24:10 -0700110 mFieldPresentations = builder.mFieldPresentations;
Felipe Leme27a026a2017-10-02 17:27:49 -0700111 mFieldFilters = builder.mFieldFilters;
Svet Ganov00c771dc2017-02-19 00:06:22 -0800112 mPresentation = builder.mPresentation;
Svet Ganov0f4928f2017-02-02 20:02:51 -0800113 mAuthentication = builder.mAuthentication;
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700114 mId = builder.mId;
Felipe Leme6d553872016-12-08 17:13:25 -0800115 }
116
117 /** @hide */
Felipe Leme640f30a2017-03-06 15:44:06 -0800118 public @Nullable ArrayList<AutofillId> getFieldIds() {
Svet Ganov0f4928f2017-02-02 20:02:51 -0800119 return mFieldIds;
Felipe Leme6d553872016-12-08 17:13:25 -0800120 }
121
122 /** @hide */
Felipe Leme640f30a2017-03-06 15:44:06 -0800123 public @Nullable ArrayList<AutofillValue> getFieldValues() {
Svet Ganov0f4928f2017-02-02 20:02:51 -0800124 return mFieldValues;
125 }
126
127 /** @hide */
Felipe Leme53112062017-03-20 13:24:10 -0700128 public RemoteViews getFieldPresentation(int index) {
129 final RemoteViews customPresentation = mFieldPresentations.get(index);
130 return customPresentation != null ? customPresentation : mPresentation;
131 }
132
133 /** @hide */
Felipe Leme27a026a2017-10-02 17:27:49 -0700134 @Nullable
Felipe Leme09d58a42018-01-30 14:04:03 -0800135 public DatasetFieldFilter getFilter(int index) {
Felipe Leme27a026a2017-10-02 17:27:49 -0700136 return mFieldFilters.get(index);
137 }
138
139 /** @hide */
Svet Ganov0f4928f2017-02-02 20:02:51 -0800140 public @Nullable IntentSender getAuthentication() {
141 return mAuthentication;
Felipe Leme436ab6a2016-12-15 11:56:15 -0800142 }
143
144 /** @hide */
145 public boolean isEmpty() {
Svet Ganov0f4928f2017-02-02 20:02:51 -0800146 return mFieldIds == null || mFieldIds.isEmpty();
Felipe Leme436ab6a2016-12-15 11:56:15 -0800147 }
148
Felipe Leme6d553872016-12-08 17:13:25 -0800149 @Override
150 public String toString() {
Felipe Leme9f9ee252017-04-27 13:56:22 -0700151 if (!sDebug) return super.toString();
Felipe Leme6d553872016-12-08 17:13:25 -0800152
Felipe Lemeda9ea342018-03-22 15:19:51 -0700153 final StringBuilder builder = new StringBuilder("Dataset[");
Felipe Leme6f12e672017-10-20 13:04:19 -0700154 if (mId == null) {
Felipe Lemeda9ea342018-03-22 15:19:51 -0700155 builder.append("noId");
Felipe Leme6f12e672017-10-20 13:04:19 -0700156 } else {
157 // Cannot disclose id because it could contain PII.
Felipe Lemeda9ea342018-03-22 15:19:51 -0700158 builder.append("id=").append(mId.length()).append("_chars");
Felipe Leme6f12e672017-10-20 13:04:19 -0700159 }
Felipe Lemeda9ea342018-03-22 15:19:51 -0700160 if (mFieldIds != null) {
161 builder.append(", fieldIds=").append(mFieldIds);
162 }
163 if (mFieldValues != null) {
164 builder.append(", fieldValues=").append(mFieldValues);
165 }
166 if (mFieldPresentations != null) {
167 builder.append(", fieldPresentations=").append(mFieldPresentations.size());
Felipe Leme6f12e672017-10-20 13:04:19 -0700168
Felipe Lemeda9ea342018-03-22 15:19:51 -0700169 }
170 if (mFieldFilters != null) {
171 builder.append(", fieldFilters=").append(mFieldFilters.size());
172 }
173 if (mPresentation != null) {
174 builder.append(", hasPresentation");
175 }
176 if (mAuthentication != null) {
177 builder.append(", hasAuthentication");
178 }
179 return builder.append(']').toString();
Felipe Leme6d553872016-12-08 17:13:25 -0800180 }
181
182 /**
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700183 * Gets the id of this dataset.
184 *
185 * @return The id of this dataset or {@code null} if not set
186 *
187 * @hide
188 */
189 public String getId() {
190 return mId;
191 }
192
193 /**
Felipe Leme2ef19c12017-06-05 11:32:32 -0700194 * A builder for {@link Dataset} objects. You must provide at least
Svet Ganov0f4928f2017-02-02 20:02:51 -0800195 * one value for a field or set an authentication intent.
Felipe Leme6d553872016-12-08 17:13:25 -0800196 */
197 public static final class Builder {
Felipe Leme640f30a2017-03-06 15:44:06 -0800198 private ArrayList<AutofillId> mFieldIds;
199 private ArrayList<AutofillValue> mFieldValues;
Felipe Leme53112062017-03-20 13:24:10 -0700200 private ArrayList<RemoteViews> mFieldPresentations;
Felipe Leme09d58a42018-01-30 14:04:03 -0800201 private ArrayList<DatasetFieldFilter> mFieldFilters;
Svet Ganov00c771dc2017-02-19 00:06:22 -0800202 private RemoteViews mPresentation;
Svet Ganov0f4928f2017-02-02 20:02:51 -0800203 private IntentSender mAuthentication;
204 private boolean mDestroyed;
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700205 @Nullable private String mId;
Svet Ganov0f4928f2017-02-02 20:02:51 -0800206
Felipe Leme6d553872016-12-08 17:13:25 -0800207 /**
Svet Ganoveb495152017-02-21 17:47:07 -0800208 * Creates a new builder.
Felipe Leme6d553872016-12-08 17:13:25 -0800209 *
Svet Ganoveb495152017-02-21 17:47:07 -0800210 * @param presentation The presentation used to visualize this dataset.
Felipe Leme6d553872016-12-08 17:13:25 -0800211 */
Svet Ganoveb495152017-02-21 17:47:07 -0800212 public Builder(@NonNull RemoteViews presentation) {
213 Preconditions.checkNotNull(presentation, "presentation must be non-null");
Svet Ganov00c771dc2017-02-19 00:06:22 -0800214 mPresentation = presentation;
Felipe Leme6d553872016-12-08 17:13:25 -0800215 }
216
217 /**
Felipe Leme53112062017-03-20 13:24:10 -0700218 * Creates a new builder for a dataset where each field will be visualized independently.
219 *
220 * <p>When using this constructor, fields must be set through
Felipe Leme3a585a82017-10-18 15:34:26 -0700221 * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
222 * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
Felipe Leme53112062017-03-20 13:24:10 -0700223 */
224 public Builder() {
225 }
226
227 /**
Felipe Leme601d2202017-11-17 08:21:23 -0800228 * Triggers a custom UI before before autofilling the screen with the contents of this
229 * dataset.
230 *
231 * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
232 * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
233 * for examples.
Felipe Leme436ab6a2016-12-15 11:56:15 -0800234 *
Svet Ganov0f4928f2017-02-02 20:02:51 -0800235 * <p>This method is called when you need to provide an authentication
Svet Ganov782043c2017-02-11 00:52:02 +0000236 * UI for the data set. For example, when a data set contains credit card information
Svet Ganov0f4928f2017-02-02 20:02:51 -0800237 * (such as number, expiration date, and verification code), you can display UI
Svet Ganov00c771dc2017-02-19 00:06:22 -0800238 * asking for the verification code before filing in the data. Even if the
Svet Ganov782043c2017-02-11 00:52:02 +0000239 * data set is completely populated the system will launch the specified authentication
240 * intent and will need your approval to fill it in. Since the data set is "locked"
241 * until the user authenticates it, typically this data set name is masked
242 * (for example, "VISA....1234"). Typically you would want to store the data set
243 * labels non-encrypted and the actual sensitive data encrypted and not in memory.
Svet Ganov0f4928f2017-02-02 20:02:51 -0800244 * This allows showing the labels in the UI while involving the user if one of
245 * the items with these labels is chosen. Note that if you use sensitive data as
Svet Ganov782043c2017-02-11 00:52:02 +0000246 * a label, for example an email address, then it should also be encrypted.</p>
Felipe Leme436ab6a2016-12-15 11:56:15 -0800247 *
Felipe Leme640f30a2017-03-06 15:44:06 -0800248 * <p>When a user triggers autofill, the system launches the provided intent
Svet Ganov782043c2017-02-11 00:52:02 +0000249 * whose extras will have the {@link
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700250 * android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen content},
251 * and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE client
252 * state}. Once you complete your authentication flow you should set the activity
253 * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
254 * {@link Dataset dataset} or a fully-populated {@link FillResponse response} by
255 * setting it to the {@link
256 * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you
257 * provide a dataset in the result, it will replace the authenticated dataset and
258 * will be immediately filled in. If you provide a response, it will replace the
259 * current response and the UI will be refreshed. For example, if you provided
260 * credit card information without the CVV for the data set in the {@link FillResponse
261 * response} then the returned data set should contain the CVV entry.
Felipe Leme436ab6a2016-12-15 11:56:15 -0800262 *
Felipe Leme27a026a2017-10-02 17:27:49 -0700263 * <p><b>Note:</b> Do not make the provided pending intent
Svet Ganov0f4928f2017-02-02 20:02:51 -0800264 * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
Felipe Leme2ef19c12017-06-05 11:32:32 -0700265 * platform needs to fill in the authentication arguments.
Svet Ganov0f4928f2017-02-02 20:02:51 -0800266 *
Svet Ganov782043c2017-02-11 00:52:02 +0000267 * @param authentication Intent to an activity with your authentication flow.
Felipe Leme27a026a2017-10-02 17:27:49 -0700268 * @return this builder.
Svet Ganov0f4928f2017-02-02 20:02:51 -0800269 *
Svet Ganov782043c2017-02-11 00:52:02 +0000270 * @see android.app.PendingIntent
Felipe Leme436ab6a2016-12-15 11:56:15 -0800271 */
Svet Ganov0f4928f2017-02-02 20:02:51 -0800272 public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) {
273 throwIfDestroyed();
274 mAuthentication = authentication;
Felipe Leme436ab6a2016-12-15 11:56:15 -0800275 return this;
276 }
277
278 /**
Felipe Leme98528972017-08-31 17:04:08 -0700279 * Sets the id for the dataset so its usage can be tracked.
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700280 *
Felipe Leme98528972017-08-31 17:04:08 -0700281 * <p>Dataset usage can be tracked for 2 purposes:
282 *
283 * <ul>
284 * <li>For statistical purposes, the service can call
285 * {@link AutofillService#getFillEventHistory()} when handling {@link
286 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
287 * calls.
288 * <li>For normal autofill workflow, the service can call
289 * {@link SaveRequest#getDatasetIds()} when handling
290 * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} calls.
291 * </ul>
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700292 *
293 * @param id id for this dataset or {@code null} to unset.
Felipe Leme98528972017-08-31 17:04:08 -0700294 *
Felipe Leme27a026a2017-10-02 17:27:49 -0700295 * @return this builder.
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700296 */
297 public @NonNull Builder setId(@Nullable String id) {
298 throwIfDestroyed();
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700299 mId = id;
300 return this;
301 }
302
303 /**
Felipe Leme6d553872016-12-08 17:13:25 -0800304 * Sets the value of a field.
305 *
Felipe Leme9856b192017-10-18 16:43:52 -0700306 * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would
307 * throw an {@link IllegalStateException} if this builder was constructed without a
308 * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and
309 * higher removed this restriction because datasets used as an
310 * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT
311 * authentication result} do not need a presentation. But if you don't set the presentation
312 * in the constructor in a dataset that is meant to be shown to the user, the autofill UI
313 * for this field will not be displayed.
314 *
Felipe Lemebbf85492017-11-21 15:00:00 -0800315 * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and
316 * higher, datasets that require authentication can be also be filtered by passing a
317 * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter.
318 *
Svet Ganov782043c2017-02-11 00:52:02 +0000319 * @param id id returned by {@link
Felipe Leme640f30a2017-03-06 15:44:06 -0800320 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
Felipe Leme2ef19c12017-06-05 11:32:32 -0700321 * @param value value to be autofilled. Pass {@code null} if you do not have the value
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700322 * but the target view is a logical part of the dataset. For example, if
Felipe Leme27a026a2017-10-02 17:27:49 -0700323 * the dataset needs authentication and you have no access to the value.
324 * @return this builder.
Felipe Leme6d553872016-12-08 17:13:25 -0800325 */
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700326 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
Svet Ganov0f4928f2017-02-02 20:02:51 -0800327 throwIfDestroyed();
Felipe Leme27a026a2017-10-02 17:27:49 -0700328 setLifeTheUniverseAndEverything(id, value, null, null);
Felipe Leme53112062017-03-20 13:24:10 -0700329 return this;
330 }
331
332 /**
Felipe Leme2ef19c12017-06-05 11:32:32 -0700333 * Sets the value of a field, using a custom {@link RemoteViews presentation} to
334 * visualize it.
Felipe Leme53112062017-03-20 13:24:10 -0700335 *
Felipe Lemebbf85492017-11-21 15:00:00 -0800336 * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and
337 * higher, datasets that require authentication can be also be filtered by passing a
338 * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter.
339 *
Dake Gu36b86c22018-04-16 12:49:30 -0700340 * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
341 * or background color: Autofill on different platforms may have different themes.
342 *
Felipe Leme53112062017-03-20 13:24:10 -0700343 * @param id id returned by {@link
344 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
Felipe Leme27a026a2017-10-02 17:27:49 -0700345 * @param value the value to be autofilled. Pass {@code null} if you do not have the value
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700346 * but the target view is a logical part of the dataset. For example, if
Felipe Leme27a026a2017-10-02 17:27:49 -0700347 * the dataset needs authentication and you have no access to the value.
348 * @param presentation the presentation used to visualize this field.
349 * @return this builder.
350 *
Felipe Leme53112062017-03-20 13:24:10 -0700351 */
Svetoslav Ganova9379d02017-05-09 17:40:24 -0700352 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
Felipe Leme53112062017-03-20 13:24:10 -0700353 @NonNull RemoteViews presentation) {
354 throwIfDestroyed();
355 Preconditions.checkNotNull(presentation, "presentation cannot be null");
Felipe Leme27a026a2017-10-02 17:27:49 -0700356 setLifeTheUniverseAndEverything(id, value, presentation, null);
Felipe Leme53112062017-03-20 13:24:10 -0700357 return this;
358 }
359
Felipe Leme27a026a2017-10-02 17:27:49 -0700360 /**
361 * Sets the value of a field using an <a href="#Filtering">explicit filter</a>.
362 *
Felipe Lemebbf85492017-11-21 15:00:00 -0800363 * <p>This method is typically used when the dataset requires authentication and the service
Felipe Leme601d2202017-11-17 08:21:23 -0800364 * does not know its value but wants to hide the dataset after the user enters a minimum
365 * number of characters. For example, if the dataset represents a credit card number and the
366 * service does not want to show the "Tap to authenticate" message until the user tapped
367 * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
Felipe Leme27a026a2017-10-02 17:27:49 -0700368 *
Felipe Lemebbf85492017-11-21 15:00:00 -0800369 * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
370 * value it's easier to filter by calling {@link #setValue(AutofillId, AutofillValue)} and
371 * use the value to filter.
372 *
Felipe Leme27a026a2017-10-02 17:27:49 -0700373 * @param id id returned by {@link
374 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
375 * @param value the value to be autofilled. Pass {@code null} if you do not have the value
376 * but the target view is a logical part of the dataset. For example, if
377 * the dataset needs authentication and you have no access to the value.
Felipe Leme09d58a42018-01-30 14:04:03 -0800378 * @param filter regex used to determine if the dataset should be shown in the autofill UI;
379 * when {@code null}, it disables filtering on that dataset (this is the recommended
380 * approach when {@code value} is not {@code null} and field contains sensitive data
381 * such as passwords).
Felipe Leme27a026a2017-10-02 17:27:49 -0700382 *
383 * @return this builder.
384 * @throws IllegalStateException if the builder was constructed without a
Felipe Leme9856b192017-10-18 16:43:52 -0700385 * {@link RemoteViews presentation}.
Felipe Leme27a026a2017-10-02 17:27:49 -0700386 */
387 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
Felipe Leme09d58a42018-01-30 14:04:03 -0800388 @Nullable Pattern filter) {
Felipe Leme27a026a2017-10-02 17:27:49 -0700389 throwIfDestroyed();
Felipe Leme27a026a2017-10-02 17:27:49 -0700390 Preconditions.checkState(mPresentation != null,
391 "Dataset presentation not set on constructor");
Felipe Leme09d58a42018-01-30 14:04:03 -0800392 setLifeTheUniverseAndEverything(id, value, null, new DatasetFieldFilter(filter));
Felipe Leme27a026a2017-10-02 17:27:49 -0700393 return this;
394 }
395
396 /**
397 * Sets the value of a field, using a custom {@link RemoteViews presentation} to
398 * visualize it and a <a href="#Filtering">explicit filter</a>.
399 *
Felipe Lemebbf85492017-11-21 15:00:00 -0800400 * <p>This method is typically used when the dataset requires authentication and the service
Felipe Leme601d2202017-11-17 08:21:23 -0800401 * does not know its value but wants to hide the dataset after the user enters a minimum
402 * number of characters. For example, if the dataset represents a credit card number and the
403 * service does not want to show the "Tap to authenticate" message until the user tapped
404 * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
Felipe Leme27a026a2017-10-02 17:27:49 -0700405 *
Felipe Lemebbf85492017-11-21 15:00:00 -0800406 * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
407 * value it's easier to filter by calling
408 * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
409 *
Felipe Leme27a026a2017-10-02 17:27:49 -0700410 * @param id id returned by {@link
411 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
412 * @param value the value to be autofilled. Pass {@code null} if you do not have the value
413 * but the target view is a logical part of the dataset. For example, if
414 * the dataset needs authentication and you have no access to the value.
Felipe Leme09d58a42018-01-30 14:04:03 -0800415 * @param filter regex used to determine if the dataset should be shown in the autofill UI;
416 * when {@code null}, it disables filtering on that dataset (this is the recommended
417 * approach when {@code value} is not {@code null} and field contains sensitive data
418 * such as passwords).
Felipe Leme27a026a2017-10-02 17:27:49 -0700419 * @param presentation the presentation used to visualize this field.
Felipe Leme27a026a2017-10-02 17:27:49 -0700420 *
421 * @return this builder.
422 */
423 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
Felipe Leme09d58a42018-01-30 14:04:03 -0800424 @Nullable Pattern filter, @NonNull RemoteViews presentation) {
Felipe Leme27a026a2017-10-02 17:27:49 -0700425 throwIfDestroyed();
Felipe Leme27a026a2017-10-02 17:27:49 -0700426 Preconditions.checkNotNull(presentation, "presentation cannot be null");
Felipe Leme09d58a42018-01-30 14:04:03 -0800427 setLifeTheUniverseAndEverything(id, value, presentation,
428 new DatasetFieldFilter(filter));
Felipe Leme27a026a2017-10-02 17:27:49 -0700429 return this;
430 }
431
432 private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
433 @Nullable AutofillValue value, @Nullable RemoteViews presentation,
Felipe Leme09d58a42018-01-30 14:04:03 -0800434 @Nullable DatasetFieldFilter filter) {
Svet Ganov0f4928f2017-02-02 20:02:51 -0800435 Preconditions.checkNotNull(id, "id cannot be null");
Svet Ganov0f4928f2017-02-02 20:02:51 -0800436 if (mFieldIds != null) {
437 final int existingIdx = mFieldIds.indexOf(id);
438 if (existingIdx >= 0) {
439 mFieldValues.set(existingIdx, value);
Felipe Leme53112062017-03-20 13:24:10 -0700440 mFieldPresentations.set(existingIdx, presentation);
Felipe Lemebbf85492017-11-21 15:00:00 -0800441 mFieldFilters.set(existingIdx, filter);
Felipe Leme53112062017-03-20 13:24:10 -0700442 return;
Svet Ganov0f4928f2017-02-02 20:02:51 -0800443 }
444 } else {
445 mFieldIds = new ArrayList<>();
446 mFieldValues = new ArrayList<>();
Felipe Leme53112062017-03-20 13:24:10 -0700447 mFieldPresentations = new ArrayList<>();
Felipe Leme27a026a2017-10-02 17:27:49 -0700448 mFieldFilters = new ArrayList<>();
Svet Ganov0f4928f2017-02-02 20:02:51 -0800449 }
450 mFieldIds.add(id);
451 mFieldValues.add(value);
Felipe Leme53112062017-03-20 13:24:10 -0700452 mFieldPresentations.add(presentation);
Felipe Leme27a026a2017-10-02 17:27:49 -0700453 mFieldFilters.add(filter);
Felipe Leme6d553872016-12-08 17:13:25 -0800454 }
455
456 /**
Felipe Leme2ef19c12017-06-05 11:32:32 -0700457 * Creates a new {@link Dataset} instance.
458 *
459 * <p>You should not interact with this builder once this method is called.
460 *
Felipe Leme27a026a2017-10-02 17:27:49 -0700461 * @throws IllegalStateException if no field was set (through
462 * {@link #setValue(AutofillId, AutofillValue)} or
463 * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}).
Svet Ganov00c771dc2017-02-19 00:06:22 -0800464 *
465 * @return The built dataset.
Svet Ganov0f4928f2017-02-02 20:02:51 -0800466 */
467 public @NonNull Dataset build() {
468 throwIfDestroyed();
469 mDestroyed = true;
Svet Ganov00c771dc2017-02-19 00:06:22 -0800470 if (mFieldIds == null) {
Felipe Leme27a026a2017-10-02 17:27:49 -0700471 throw new IllegalStateException("at least one value must be set");
Svet Ganov00c771dc2017-02-19 00:06:22 -0800472 }
Felipe Leme6d553872016-12-08 17:13:25 -0800473 return new Dataset(this);
474 }
475
Svet Ganov0f4928f2017-02-02 20:02:51 -0800476 private void throwIfDestroyed() {
477 if (mDestroyed) {
478 throw new IllegalStateException("Already called #build()");
479 }
Felipe Leme6d553872016-12-08 17:13:25 -0800480 }
481 }
482
483 /////////////////////////////////////
484 // Parcelable "contract" methods. //
485 /////////////////////////////////////
486
487 @Override
488 public int describeContents() {
489 return 0;
490 }
491
492 @Override
493 public void writeToParcel(Parcel parcel, int flags) {
Svet Ganoveb495152017-02-21 17:47:07 -0800494 parcel.writeParcelable(mPresentation, flags);
Sunny Goyal0e60f222017-09-21 21:39:20 -0700495 parcel.writeTypedList(mFieldIds, flags);
496 parcel.writeTypedList(mFieldValues, flags);
Felipe Leme09d58a42018-01-30 14:04:03 -0800497 parcel.writeTypedList(mFieldPresentations, flags);
498 parcel.writeTypedList(mFieldFilters, flags);
Svet Ganov0f4928f2017-02-02 20:02:51 -0800499 parcel.writeParcelable(mAuthentication, flags);
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700500 parcel.writeString(mId);
Felipe Leme6d553872016-12-08 17:13:25 -0800501 }
502
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700503 public static final @android.annotation.NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
Felipe Leme6d553872016-12-08 17:13:25 -0800504 @Override
Svet Ganov0f4928f2017-02-02 20:02:51 -0800505 public Dataset createFromParcel(Parcel parcel) {
506 // Always go through the builder to ensure the data ingested by
507 // the system obeys the contract of the builder to avoid attacks
508 // using specially crafted parcels.
Felipe Leme53112062017-03-20 13:24:10 -0700509 final RemoteViews presentation = parcel.readParcelable(null);
510 final Builder builder = (presentation == null)
511 ? new Builder()
512 : new Builder(presentation);
Felipe Leme09d58a42018-01-30 14:04:03 -0800513 final ArrayList<AutofillId> ids =
514 parcel.createTypedArrayList(AutofillId.CREATOR);
Sunny Goyal0e60f222017-09-21 21:39:20 -0700515 final ArrayList<AutofillValue> values =
516 parcel.createTypedArrayList(AutofillValue.CREATOR);
Felipe Leme09d58a42018-01-30 14:04:03 -0800517 final ArrayList<RemoteViews> presentations =
518 parcel.createTypedArrayList(RemoteViews.CREATOR);
519 final ArrayList<DatasetFieldFilter> filters =
520 parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
521 for (int i = 0; i < ids.size(); i++) {
Felipe Leme640f30a2017-03-06 15:44:06 -0800522 final AutofillId id = ids.get(i);
Felipe Leme09d58a42018-01-30 14:04:03 -0800523 final AutofillValue value = values.get(i);
524 final RemoteViews fieldPresentation = presentations.get(i);
525 final DatasetFieldFilter filter = filters.get(i);
Felipe Leme27a026a2017-10-02 17:27:49 -0700526 builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter);
Svet Ganov0f4928f2017-02-02 20:02:51 -0800527 }
Svet Ganov0f4928f2017-02-02 20:02:51 -0800528 builder.setAuthentication(parcel.readParcelable(null));
Philip P. Moltmanncc684ed2017-04-17 14:18:33 -0700529 builder.setId(parcel.readString());
Svet Ganov0f4928f2017-02-02 20:02:51 -0800530 return builder.build();
Felipe Leme6d553872016-12-08 17:13:25 -0800531 }
532
533 @Override
534 public Dataset[] newArray(int size) {
535 return new Dataset[size];
536 }
537 };
Felipe Leme09d58a42018-01-30 14:04:03 -0800538
539 /**
540 * Helper class used to indicate when the service explicitly set a {@link Pattern} filter for a
541 * dataset field&dash; we cannot use a {@link Pattern} directly because then we wouldn't be
542 * able to differentiate whether the service explicitly passed a {@code null} filter to disable
543 * filter, or when it called the methods that does not take a filter {@link Pattern}.
544 *
545 * @hide
546 */
547 public static final class DatasetFieldFilter implements Parcelable {
548
549 @Nullable
550 public final Pattern pattern;
551
552 private DatasetFieldFilter(@Nullable Pattern pattern) {
553 this.pattern = pattern;
554 }
555
556 @Override
557 public String toString() {
558 if (!sDebug) return super.toString();
559
560 // Cannot log pattern because it could contain PII
561 return pattern == null ? "null" : pattern.pattern().length() + "_chars";
562 }
563
564 @Override
565 public int describeContents() {
566 return 0;
567 }
568
569 @Override
570 public void writeToParcel(Parcel parcel, int flags) {
571 parcel.writeSerializable(pattern);
572 }
573
574 @SuppressWarnings("hiding")
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700575 public static final @android.annotation.NonNull Creator<DatasetFieldFilter> CREATOR =
Felipe Leme09d58a42018-01-30 14:04:03 -0800576 new Creator<DatasetFieldFilter>() {
577
578 @Override
579 public DatasetFieldFilter createFromParcel(Parcel parcel) {
580 return new DatasetFieldFilter((Pattern) parcel.readSerializable());
581 }
582
583 @Override
584 public DatasetFieldFilter[] newArray(int size) {
585 return new DatasetFieldFilter[size];
586 }
587 };
588 }
Felipe Leme6d553872016-12-08 17:13:25 -0800589}