blob: 94b9d050a44dc65cbaf8a47033009d99bf9cf1c7 [file] [log] [blame]
Felipe Lemef69761f2017-02-23 17:52:01 -08001/*
2 * Copyright (C) 2017 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
Felipe Leme25960892018-03-21 12:56:05 -070019import static android.service.autofill.AutofillServiceHelper.assertValid;
Felipe Leme9f9ee252017-04-27 13:56:22 -070020import static android.view.autofill.Helper.sDebug;
Felipe Lemef69761f2017-02-23 17:52:01 -080021
22import android.annotation.IntDef;
23import android.annotation.NonNull;
24import android.annotation.Nullable;
Felipe Leme2ef19c12017-06-05 11:32:32 -070025import android.app.Activity;
Svet Ganov33d06fc2017-03-01 10:38:34 -080026import android.content.IntentSender;
Felipe Lemef69761f2017-02-23 17:52:01 -080027import android.os.Parcel;
28import android.os.Parcelable;
Felipe Lemecd2969c2017-10-05 08:56:04 -070029import android.util.ArrayMap;
30import android.util.ArraySet;
Felipe Leme7d5adb52017-04-06 17:57:43 -070031import android.util.DebugUtils;
Felipe Leme640f30a2017-03-06 15:44:06 -080032import android.view.autofill.AutofillId;
Felipe Leme82e37932017-03-10 10:05:56 -080033import android.view.autofill.AutofillManager;
34import android.view.autofill.AutofillValue;
35
Felipe Leme7fc29dd2017-07-17 11:38:40 -070036import com.android.internal.util.ArrayUtils;
Felipe Leme82e37932017-03-10 10:05:56 -080037import com.android.internal.util.Preconditions;
Felipe Lemef69761f2017-02-23 17:52:01 -080038
39import java.lang.annotation.Retention;
40import java.lang.annotation.RetentionPolicy;
Felipe Leme82e37932017-03-10 10:05:56 -080041import java.util.Arrays;
Felipe Lemef69761f2017-02-23 17:52:01 -080042
43/**
Felipe Leme82e37932017-03-10 10:05:56 -080044 * Information used to indicate that an {@link AutofillService} is interested on saving the
45 * user-inputed data for future use, through a
Felipe Lemee5f9c302017-04-18 17:48:49 -070046 * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
Felipe Leme82e37932017-03-10 10:05:56 -080047 * call.
Felipe Lemef69761f2017-02-23 17:52:01 -080048 *
Felipe Leme82e37932017-03-10 10:05:56 -080049 * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least
50 * two pieces of information:
Felipe Lemef69761f2017-02-23 17:52:01 -080051 *
Felipe Leme82e37932017-03-10 10:05:56 -080052 * <ol>
Felipe Leme2ef19c12017-06-05 11:32:32 -070053 * <li>The type(s) of user data (like password or credit card info) that would be saved.
Felipe Leme82e37932017-03-10 10:05:56 -080054 * <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed
55 * to trigger a save request.
56 * </ol>
Felipe Lemef69761f2017-02-23 17:52:01 -080057 *
Felipe Leme2ef19c12017-06-05 11:32:32 -070058 * <p>Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}:
Felipe Leme82e37932017-03-10 10:05:56 -080059 *
60 * <pre class="prettyprint">
Felipe Leme2ef19c12017-06-05 11:32:32 -070061 * new FillResponse.Builder()
62 * .addDataset(new Dataset.Builder()
63 * .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer")) // username
64 * .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer")) // password
65 * .build())
66 * .setSaveInfo(new SaveInfo.Builder(
67 * SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
68 * new AutofillId[] { id1, id2 }).build())
69 * .build();
Felipe Leme82e37932017-03-10 10:05:56 -080070 * </pre>
71 *
Felipe Leme2fe3ade2017-09-28 15:03:36 -070072 * <p>The save type flags are used to display the appropriate strings in the autofill save UI.
Felipe Leme2ef19c12017-06-05 11:32:32 -070073 * You can pass multiple values, but try to keep it short if possible. In the above example, just
74 * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough.
75 *
76 * <p>There might be cases where the {@link AutofillService} knows how to fill the screen,
77 * but the user has no data for it. In that case, the {@link FillResponse} should contain just the
78 * {@link SaveInfo}, but no {@link Dataset Datasets}:
Felipe Leme82e37932017-03-10 10:05:56 -080079 *
80 * <pre class="prettyprint">
Felipe Leme2ef19c12017-06-05 11:32:32 -070081 * new FillResponse.Builder()
82 * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD,
83 * new AutofillId[] { id1, id2 }).build())
84 * .build();
Felipe Leme82e37932017-03-10 10:05:56 -080085 * </pre>
86 *
87 * <p>There might be cases where the user data in the {@link AutofillService} is enough
88 * to populate some fields but not all, and the service would still be interested on saving the
Felipe Leme2ef19c12017-06-05 11:32:32 -070089 * other fields. In that case, the service could set the
Felipe Leme82e37932017-03-10 10:05:56 -080090 * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well:
91 *
92 * <pre class="prettyprint">
93 * new FillResponse.Builder()
Felipe Leme2ef19c12017-06-05 11:32:32 -070094 * .addDataset(new Dataset.Builder()
95 * .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"),
96 * createPresentation("742 Evergreen Terrace")) // street
97 * .setValue(id2, AutofillValue.forText("Springfield"),
98 * createPresentation("Springfield")) // city
99 * .build())
100 * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS,
101 * new AutofillId[] { id1, id2 }) // street and city
102 * .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode
103 * .build())
Felipe Leme82e37932017-03-10 10:05:56 -0800104 * .build();
105 * </pre>
106 *
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700107 * <a name="TriggeringSaveRequest"></a>
108 * <h3>Triggering a save request</h3>
109 *
Felipe Leme2ef19c12017-06-05 11:32:32 -0700110 * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after
111 * any of the following events:
112 * <ul>
113 * <li>The {@link Activity} finishes.
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700114 * <li>The app explicitly calls {@link AutofillManager#commit()}.
115 * <li>All required views become invisible (if the {@link SaveInfo} was created with the
Felipe Leme2ef19c12017-06-05 11:32:32 -0700116 * {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag).
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700117 * <li>The user clicks a specific view (defined by {@link Builder#setTriggerId(AutofillId)}.
Felipe Leme2ef19c12017-06-05 11:32:32 -0700118 * </ul>
Felipe Leme82e37932017-03-10 10:05:56 -0800119 *
Felipe Leme2ef19c12017-06-05 11:32:32 -0700120 * <p>But it is only triggered when all conditions below are met:
121 * <ul>
Felipe Leme8d7f7f42018-07-31 16:14:39 -0700122 * <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null} neither
123 * has the {@link #FLAG_DELAY_SAVE} flag.
Felipe Leme2ef19c12017-06-05 11:32:32 -0700124 * <li>The {@link AutofillValue}s of all required views (as set by the {@code requiredIds} passed
125 * to the {@link SaveInfo.Builder} constructor are not empty.
Felipe Leme82e37932017-03-10 10:05:56 -0800126 * <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed
Felipe Leme2ef19c12017-06-05 11:32:32 -0700127 * (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value
128 * presented in the view).
Felipe Lemed0b18d62017-07-24 17:08:26 -0700129 * <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the
130 * screen state (i.e., all required and optional fields in the dataset have the same value as
131 * the fields in the screen).
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700132 * <li>The user explicitly tapped the autofill save UI asking to save data for autofill.
Felipe Leme2ef19c12017-06-05 11:32:32 -0700133 * </ul>
134 *
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700135 * <a name="CustomizingSaveUI"></a>
136 * <h3>Customizing the autofill save UI</h3>
137 *
138 * <p>The service can also customize some aspects of the autofill save UI:
Felipe Leme2ef19c12017-06-05 11:32:32 -0700139 * <ul>
Felipe Leme979013d2017-06-22 10:59:23 -0700140 * <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}.
141 * <li>Add a customized subtitle by calling
142 * {@link Builder#setCustomDescription(CustomDescription)}.
Felipe Leme2ef19c12017-06-05 11:32:32 -0700143 * <li>Customize the button used to reject the save request by calling
144 * {@link Builder#setNegativeAction(int, IntentSender)}.
Felipe Leme979013d2017-06-22 10:59:23 -0700145 * <li>Decide whether the UI should be shown based on the user input validation by calling
146 * {@link Builder#setValidator(Validator)}.
Felipe Leme2ef19c12017-06-05 11:32:32 -0700147 * </ul>
Felipe Lemef69761f2017-02-23 17:52:01 -0800148 */
149public final class SaveInfo implements Parcelable {
150
151 /**
Felipe Leme2ef19c12017-06-05 11:32:32 -0700152 * Type used when the service can save the contents of a screen, but cannot describe what
Felipe Lemef69761f2017-02-23 17:52:01 -0800153 * the content is for.
154 */
Felipe Leme7d5adb52017-04-06 17:57:43 -0700155 public static final int SAVE_DATA_TYPE_GENERIC = 0x0;
Felipe Lemef69761f2017-02-23 17:52:01 -0800156
157 /**
Felipe Lemeb72f0122017-02-24 12:53:27 -0800158 * Type used when the {@link FillResponse} represents user credentials that have a password.
Felipe Lemef69761f2017-02-23 17:52:01 -0800159 */
Felipe Leme7d5adb52017-04-06 17:57:43 -0700160 public static final int SAVE_DATA_TYPE_PASSWORD = 0x01;
Felipe Lemeb72f0122017-02-24 12:53:27 -0800161
Felipe Lemef69761f2017-02-23 17:52:01 -0800162 /**
163 * Type used on when the {@link FillResponse} represents a physical address (such as street,
164 * city, state, etc).
165 */
Felipe Leme7d5adb52017-04-06 17:57:43 -0700166 public static final int SAVE_DATA_TYPE_ADDRESS = 0x02;
Felipe Lemef69761f2017-02-23 17:52:01 -0800167
168 /**
Felipe Lemeb72f0122017-02-24 12:53:27 -0800169 * Type used when the {@link FillResponse} represents a credit card.
Felipe Lemef69761f2017-02-23 17:52:01 -0800170 */
Felipe Leme7d5adb52017-04-06 17:57:43 -0700171 public static final int SAVE_DATA_TYPE_CREDIT_CARD = 0x04;
Felipe Lemef69761f2017-02-23 17:52:01 -0800172
Felipe Leme09622622017-04-04 10:13:08 -0700173 /**
174 * Type used when the {@link FillResponse} represents just an username, without a password.
175 */
Felipe Leme7d5adb52017-04-06 17:57:43 -0700176 public static final int SAVE_DATA_TYPE_USERNAME = 0x08;
Felipe Leme09622622017-04-04 10:13:08 -0700177
178 /**
179 * Type used when the {@link FillResponse} represents just an email address, without a password.
180 */
Felipe Leme7d5adb52017-04-06 17:57:43 -0700181 public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 0x10;
Felipe Leme09622622017-04-04 10:13:08 -0700182
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700183 /**
184 * Style for the negative button of the save UI to cancel the
185 * save operation. In this case, the user tapping the negative
186 * button signals that they would prefer to not save the filled
187 * content.
188 */
189 public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0;
190
191 /**
192 * Style for the negative button of the save UI to reject the
193 * save operation. This could be useful if the user needs to
194 * opt-in your service and the save prompt is an advertisement
195 * of the potential value you can add to the user. In this
196 * case, the user tapping the negative button sends a strong
197 * signal that the feature may not be useful and you may
198 * consider some backoff strategy.
199 */
200 public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1;
201
202 /** @hide */
Jeff Sharkeyce8db992017-12-13 20:05:05 -0700203 @IntDef(prefix = { "NEGATIVE_BUTTON_STYLE_" }, value = {
204 NEGATIVE_BUTTON_STYLE_CANCEL,
205 NEGATIVE_BUTTON_STYLE_REJECT
206 })
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700207 @Retention(RetentionPolicy.SOURCE)
208 @interface NegativeButtonStyle{}
209
Svet Ganov013efe12017-04-13 21:56:16 -0700210 /** @hide */
Jeff Sharkeyce8db992017-12-13 20:05:05 -0700211 @IntDef(flag = true, prefix = { "SAVE_DATA_TYPE_" }, value = {
212 SAVE_DATA_TYPE_GENERIC,
213 SAVE_DATA_TYPE_PASSWORD,
214 SAVE_DATA_TYPE_ADDRESS,
215 SAVE_DATA_TYPE_CREDIT_CARD,
216 SAVE_DATA_TYPE_USERNAME,
217 SAVE_DATA_TYPE_EMAIL_ADDRESS
218 })
Svet Ganov013efe12017-04-13 21:56:16 -0700219 @Retention(RetentionPolicy.SOURCE)
220 @interface SaveDataType{}
221
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700222 /**
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700223 * Usually, a save request is only automatically <a href="#TriggeringSaveRequest">triggered</a>
224 * once the {@link Activity} finishes. If this flag is set, it is triggered once all saved views
225 * become invisible.
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700226 */
227 public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
228
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700229 /**
230 * By default, a save request is automatically <a href="#TriggeringSaveRequest">triggered</a>
231 * once the {@link Activity} finishes. If this flag is set, finishing the activity doesn't
232 * trigger a save request.
233 *
234 * <p>This flag is typically used in conjunction with {@link Builder#setTriggerId(AutofillId)}.
235 */
236 public static final int FLAG_DONT_SAVE_ON_FINISH = 0x2;
237
Felipe Leme7727bcc2018-09-14 10:13:27 -0700238
Felipe Leme8d7f7f42018-07-31 16:14:39 -0700239 /**
Felipe Leme7727bcc2018-09-14 10:13:27 -0700240 * Postpone the autofill save UI.
241 *
242 * <p>If flag is set, the autofill save UI is not triggered when the
243 * autofill context associated with the response associated with this {@link SaveInfo} is
244 * committed (with {@link AutofillManager#commit()}). Instead, the {@link FillContext}
245 * is delivered in future fill requests (with {@link
246 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)})
247 * and save request (with {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)})
248 * of an activity belonging to the same task.
Felipe Leme8d7f7f42018-07-31 16:14:39 -0700249 *
250 * <p>This flag should be used when the service detects that the application uses
251 * multiple screens to implement an autofillable workflow (for example, one screen for the
252 * username field, another for password).
253 */
Felipe Lemec7ee7af2018-08-27 12:36:16 -0700254 // TODO(b/113281366): improve documentation: add example, document relationship with other
Felipe Leme8d7f7f42018-07-31 16:14:39 -0700255 // flagss, etc...
256 public static final int FLAG_DELAY_SAVE = 0x4;
257
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700258 /** @hide */
Jeff Sharkeyce8db992017-12-13 20:05:05 -0700259 @IntDef(flag = true, prefix = { "FLAG_" }, value = {
260 FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE,
Felipe Leme8d7f7f42018-07-31 16:14:39 -0700261 FLAG_DONT_SAVE_ON_FINISH,
262 FLAG_DELAY_SAVE
Jeff Sharkeyce8db992017-12-13 20:05:05 -0700263 })
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700264 @Retention(RetentionPolicy.SOURCE)
265 @interface SaveInfoFlags{}
266
Svet Ganov013efe12017-04-13 21:56:16 -0700267 private final @SaveDataType int mType;
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700268 private final @NegativeButtonStyle int mNegativeButtonStyle;
Felipe Leme82e37932017-03-10 10:05:56 -0800269 private final IntentSender mNegativeActionListener;
270 private final AutofillId[] mRequiredIds;
271 private final AutofillId[] mOptionalIds;
Felipe Lemeb72f0122017-02-24 12:53:27 -0800272 private final CharSequence mDescription;
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700273 private final int mFlags;
Felipe Leme979013d2017-06-22 10:59:23 -0700274 private final CustomDescription mCustomDescription;
275 private final InternalValidator mValidator;
Felipe Lemecd2969c2017-10-05 08:56:04 -0700276 private final InternalSanitizer[] mSanitizerKeys;
277 private final AutofillId[][] mSanitizerValues;
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700278 private final AutofillId mTriggerId;
Felipe Lemef69761f2017-02-23 17:52:01 -0800279
Felipe Lemef69761f2017-02-23 17:52:01 -0800280 private SaveInfo(Builder builder) {
281 mType = builder.mType;
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700282 mNegativeButtonStyle = builder.mNegativeButtonStyle;
Svet Ganov33d06fc2017-03-01 10:38:34 -0800283 mNegativeActionListener = builder.mNegativeActionListener;
Felipe Leme82e37932017-03-10 10:05:56 -0800284 mRequiredIds = builder.mRequiredIds;
285 mOptionalIds = builder.mOptionalIds;
Felipe Lemeb72f0122017-02-24 12:53:27 -0800286 mDescription = builder.mDescription;
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700287 mFlags = builder.mFlags;
Felipe Leme979013d2017-06-22 10:59:23 -0700288 mCustomDescription = builder.mCustomDescription;
289 mValidator = builder.mValidator;
Felipe Lemecd2969c2017-10-05 08:56:04 -0700290 if (builder.mSanitizers == null) {
291 mSanitizerKeys = null;
292 mSanitizerValues = null;
293 } else {
294 final int size = builder.mSanitizers.size();
295 mSanitizerKeys = new InternalSanitizer[size];
296 mSanitizerValues = new AutofillId[size][];
297 for (int i = 0; i < size; i++) {
298 mSanitizerKeys[i] = builder.mSanitizers.keyAt(i);
299 mSanitizerValues[i] = builder.mSanitizers.valueAt(i);
300 }
301 }
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700302 mTriggerId = builder.mTriggerId;
Felipe Lemef69761f2017-02-23 17:52:01 -0800303 }
304
305 /** @hide */
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700306 public @NegativeButtonStyle int getNegativeActionStyle() {
307 return mNegativeButtonStyle;
Svet Ganov33d06fc2017-03-01 10:38:34 -0800308 }
309
310 /** @hide */
311 public @Nullable IntentSender getNegativeActionListener() {
312 return mNegativeActionListener;
313 }
314
315 /** @hide */
Felipe Leme7fc29dd2017-07-17 11:38:40 -0700316 public @Nullable AutofillId[] getRequiredIds() {
Felipe Leme82e37932017-03-10 10:05:56 -0800317 return mRequiredIds;
318 }
319
320 /** @hide */
321 public @Nullable AutofillId[] getOptionalIds() {
322 return mOptionalIds;
Felipe Lemef69761f2017-02-23 17:52:01 -0800323 }
324
325 /** @hide */
Svet Ganov013efe12017-04-13 21:56:16 -0700326 public @SaveDataType int getType() {
Felipe Lemeb72f0122017-02-24 12:53:27 -0800327 return mType;
328 }
329
330 /** @hide */
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700331 public @SaveInfoFlags int getFlags() {
332 return mFlags;
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700333 }
334
335 /** @hide */
Felipe Lemeb72f0122017-02-24 12:53:27 -0800336 public CharSequence getDescription() {
337 return mDescription;
338 }
339
Felipe Leme979013d2017-06-22 10:59:23 -0700340 /** @hide */
341 @Nullable
342 public CustomDescription getCustomDescription() {
343 return mCustomDescription;
344 }
345
346 /** @hide */
347 @Nullable
348 public InternalValidator getValidator() {
349 return mValidator;
350 }
351
Felipe Lemecd2969c2017-10-05 08:56:04 -0700352 /** @hide */
353 @Nullable
354 public InternalSanitizer[] getSanitizerKeys() {
355 return mSanitizerKeys;
356 }
357
358 /** @hide */
359 @Nullable
360 public AutofillId[][] getSanitizerValues() {
361 return mSanitizerValues;
362 }
363
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700364 /** @hide */
365 @Nullable
366 public AutofillId getTriggerId() {
367 return mTriggerId;
368 }
369
Felipe Lemef69761f2017-02-23 17:52:01 -0800370 /**
371 * A builder for {@link SaveInfo} objects.
372 */
373 public static final class Builder {
374
Svet Ganov013efe12017-04-13 21:56:16 -0700375 private final @SaveDataType int mType;
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700376 private @NegativeButtonStyle int mNegativeButtonStyle = NEGATIVE_BUTTON_STYLE_CANCEL;
Svet Ganov33d06fc2017-03-01 10:38:34 -0800377 private IntentSender mNegativeActionListener;
Felipe Leme0d3db062017-04-21 08:08:39 -0700378 private final AutofillId[] mRequiredIds;
Felipe Leme82e37932017-03-10 10:05:56 -0800379 private AutofillId[] mOptionalIds;
Felipe Lemeb72f0122017-02-24 12:53:27 -0800380 private CharSequence mDescription;
Felipe Lemef69761f2017-02-23 17:52:01 -0800381 private boolean mDestroyed;
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700382 private int mFlags;
Felipe Leme979013d2017-06-22 10:59:23 -0700383 private CustomDescription mCustomDescription;
384 private InternalValidator mValidator;
Felipe Lemecd2969c2017-10-05 08:56:04 -0700385 private ArrayMap<InternalSanitizer, AutofillId[]> mSanitizers;
386 // Set used to validate against duplicate ids.
387 private ArraySet<AutofillId> mSanitizerIds;
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700388 private AutofillId mTriggerId;
Felipe Lemef69761f2017-02-23 17:52:01 -0800389
390 /**
391 * Creates a new builder.
392 *
Felipe Leme979013d2017-06-22 10:59:23 -0700393 * @param type the type of information the associated {@link FillResponse} represents. It
394 * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
Felipe Leme7d5adb52017-04-06 17:57:43 -0700395 * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
396 * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
397 * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
398 * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
Felipe Leme82e37932017-03-10 10:05:56 -0800399 * @param requiredIds ids of all required views that will trigger a save request.
400 *
401 * <p>See {@link SaveInfo} for more info.
402 *
Felipe Leme18d0ef72017-06-15 15:29:23 -0700403 * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty, or if
404 * it contains any {@code null} entry.
Felipe Lemef69761f2017-02-23 17:52:01 -0800405 */
Svet Ganov013efe12017-04-13 21:56:16 -0700406 public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
Felipe Leme7d5adb52017-04-06 17:57:43 -0700407 mType = type;
Felipe Leme18d0ef72017-06-15 15:29:23 -0700408 mRequiredIds = assertValid(requiredIds);
409 }
410
Felipe Leme7fc29dd2017-07-17 11:38:40 -0700411 /**
412 * Creates a new builder when no id is required.
413 *
414 * <p>When using this builder, caller must call {@link #setOptionalIds(AutofillId[])} before
415 * calling {@link #build()}.
416 *
417 * @param type the type of information the associated {@link FillResponse} represents. It
418 * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
419 * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
420 * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
421 * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
422 * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
423 *
424 * <p>See {@link SaveInfo} for more info.
425 */
426 public Builder(@SaveDataType int type) {
427 mType = type;
428 mRequiredIds = null;
429 }
430
Felipe Lemef69761f2017-02-23 17:52:01 -0800431 /**
Felipe Leme2ef19c12017-06-05 11:32:32 -0700432 * Sets flags changing the save behavior.
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700433 *
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700434 * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE},
Felipe Leme8d7f7f42018-07-31 16:14:39 -0700435 * {@link #FLAG_DONT_SAVE_ON_FINISH}, {@link #FLAG_DELAY_SAVE}, or {@code 0}.
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700436 * @return This builder.
437 */
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700438 public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700439 throwIfDestroyed();
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700440
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700441 mFlags = Preconditions.checkFlagsArgument(flags,
Felipe Leme8d7f7f42018-07-31 16:14:39 -0700442 FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE | FLAG_DONT_SAVE_ON_FINISH
443 | FLAG_DELAY_SAVE);
Philip P. Moltmann494c3f52017-04-11 10:13:33 -0700444 return this;
445 }
446
447 /**
Felipe Leme82e37932017-03-10 10:05:56 -0800448 * Sets the ids of additional, optional views the service would be interested to save.
449 *
450 * <p>See {@link SaveInfo} for more info.
451 *
452 * @param ids The ids of the optional views.
453 * @return This builder.
Felipe Leme18d0ef72017-06-15 15:29:23 -0700454 *
455 * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
456 * it contains any {@code null} entry.
Felipe Leme82e37932017-03-10 10:05:56 -0800457 */
Felipe Leme18d0ef72017-06-15 15:29:23 -0700458 public @NonNull Builder setOptionalIds(@NonNull AutofillId[] ids) {
Felipe Leme82e37932017-03-10 10:05:56 -0800459 throwIfDestroyed();
Felipe Leme18d0ef72017-06-15 15:29:23 -0700460 mOptionalIds = assertValid(ids);
Felipe Lemef69761f2017-02-23 17:52:01 -0800461 return this;
462 }
463
464 /**
Felipe Lemeb72f0122017-02-24 12:53:27 -0800465 * Sets an optional description to be shown in the UI when the user is asked to save.
466 *
467 * <p>Typically, it describes how the data will be stored by the service, so it can help
468 * users to decide whether they can trust the service to save their data.
469 *
470 * @param description a succint description.
471 * @return This Builder.
Felipe Leme979013d2017-06-22 10:59:23 -0700472 *
473 * @throws IllegalStateException if this call was made after calling
474 * {@link #setCustomDescription(CustomDescription)}.
Felipe Lemeb72f0122017-02-24 12:53:27 -0800475 */
476 public @NonNull Builder setDescription(@Nullable CharSequence description) {
Felipe Leme82e37932017-03-10 10:05:56 -0800477 throwIfDestroyed();
Felipe Leme979013d2017-06-22 10:59:23 -0700478 Preconditions.checkState(mCustomDescription == null,
479 "Can call setDescription() or setCustomDescription(), but not both");
Felipe Lemeb72f0122017-02-24 12:53:27 -0800480 mDescription = description;
481 return this;
482 }
483
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700484 /**
Felipe Leme979013d2017-06-22 10:59:23 -0700485 * Sets a custom description to be shown in the UI when the user is asked to save.
486 *
487 * <p>Typically used when the service must show more info about the object being saved,
488 * like a credit card logo, masked number, and expiration date.
489 *
490 * @param customDescription the custom description.
491 * @return This Builder.
492 *
493 * @throws IllegalStateException if this call was made after calling
494 * {@link #setDescription(CharSequence)}.
495 */
496 public @NonNull Builder setCustomDescription(@NonNull CustomDescription customDescription) {
497 throwIfDestroyed();
498 Preconditions.checkState(mDescription == null,
499 "Can call setDescription() or setCustomDescription(), but not both");
500 mCustomDescription = customDescription;
501 return this;
502 }
503
504 /**
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700505 * Sets the style and listener for the negative save action.
506 *
Felipe Leme979013d2017-06-22 10:59:23 -0700507 * <p>This allows an autofill service to customize the style and be
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700508 * notified when the user selects the negative action in the save
509 * UI. Note that selecting the negative action regardless of its style
510 * and listener being customized would dismiss the save UI and if a
Felipe Leme979013d2017-06-22 10:59:23 -0700511 * custom listener intent is provided then this intent is
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700512 * started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p>
513 *
514 * @param style The action style.
515 * @param listener The action listener.
516 * @return This builder.
517 *
518 * @see #NEGATIVE_BUTTON_STYLE_CANCEL
519 * @see #NEGATIVE_BUTTON_STYLE_REJECT
520 *
521 * @throws IllegalArgumentException If the style is invalid
522 */
523 public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style,
524 @Nullable IntentSender listener) {
525 throwIfDestroyed();
526 if (style != NEGATIVE_BUTTON_STYLE_CANCEL
527 && style != NEGATIVE_BUTTON_STYLE_REJECT) {
528 throw new IllegalArgumentException("Invalid style: " + style);
Svet Ganov33d06fc2017-03-01 10:38:34 -0800529 }
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700530 mNegativeButtonStyle = style;
Svet Ganov33d06fc2017-03-01 10:38:34 -0800531 mNegativeActionListener = listener;
532 return this;
533 }
534
535 /**
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700536 * Sets an object used to validate the user input - if the input is not valid, the
537 * autofill save UI is not shown.
Felipe Leme979013d2017-06-22 10:59:23 -0700538 *
539 * <p>Typically used to validate credit card numbers. Examples:
540 *
541 * <p>Validator for a credit number that must have exactly 16 digits:
542 *
543 * <pre class="prettyprint">
Felipe Lemec7cea5b2017-08-02 09:50:15 -0700544 * Validator validator = new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$"))
Felipe Leme979013d2017-06-22 10:59:23 -0700545 * </pre>
546 *
547 * <p>Validator for a credit number that must pass a Luhn checksum and either have
548 * 16 digits, or 15 digits starting with 108:
549 *
550 * <pre class="prettyprint">
Felipe Leme5e0bfe72017-10-26 09:23:43 -0700551 * import static android.service.autofill.Validators.and;
552 * import static android.service.autofill.Validators.or;
Felipe Leme979013d2017-06-22 10:59:23 -0700553 *
554 * Validator validator =
555 * and(
556 * new LuhnChecksumValidator(ccNumberId),
557 * or(
Felipe Leme5e0bfe72017-10-26 09:23:43 -0700558 * new RegexValidator(ccNumberId, Pattern.compile("^\\d{16}$")),
559 * new RegexValidator(ccNumberId, Pattern.compile("^108\\d{12}$"))
Felipe Leme979013d2017-06-22 10:59:23 -0700560 * )
561 * );
562 * </pre>
563 *
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700564 * <p><b>Note:</b> the example above is just for illustrative purposes; the same validator
Felipe Leme979013d2017-06-22 10:59:23 -0700565 * could be created using a single regex for the {@code OR} part:
566 *
567 * <pre class="prettyprint">
568 * Validator validator =
569 * and(
570 * new LuhnChecksumValidator(ccNumberId),
Felipe Lemec7cea5b2017-08-02 09:50:15 -0700571 * new RegexValidator(ccNumberId, Pattern.compile(""^(\\d{16}|108\\d{12})$"))
Felipe Leme979013d2017-06-22 10:59:23 -0700572 * );
573 * </pre>
574 *
575 * <p>Validator for a credit number contained in just 4 fields and that must have exactly
576 * 4 digits on each field:
577 *
578 * <pre class="prettyprint">
Felipe Leme5e0bfe72017-10-26 09:23:43 -0700579 * import static android.service.autofill.Validators.and;
Felipe Leme979013d2017-06-22 10:59:23 -0700580 *
581 * Validator validator =
582 * and(
Felipe Leme5e0bfe72017-10-26 09:23:43 -0700583 * new RegexValidator(ccNumberId1, Pattern.compile("^\\d{4}$")),
584 * new RegexValidator(ccNumberId2, Pattern.compile("^\\d{4}$")),
585 * new RegexValidator(ccNumberId3, Pattern.compile("^\\d{4}$")),
586 * new RegexValidator(ccNumberId4, Pattern.compile("^\\d{4}$"))
Felipe Leme979013d2017-06-22 10:59:23 -0700587 * );
588 * </pre>
589 *
590 * @param validator an implementation provided by the Android System.
591 * @return this builder.
592 *
593 * @throws IllegalArgumentException if {@code validator} is not a class provided
594 * by the Android System.
595 */
596 public @NonNull Builder setValidator(@NonNull Validator validator) {
597 throwIfDestroyed();
598 Preconditions.checkArgument((validator instanceof InternalValidator),
599 "not provided by Android System: " + validator);
600 mValidator = (InternalValidator) validator;
601 return this;
602 }
603
604 /**
Felipe Lemecd2969c2017-10-05 08:56:04 -0700605 * Adds a sanitizer for one or more field.
606 *
607 * <p>When a sanitizer is set for a field, the {@link AutofillValue} is sent to the
608 * sanitizer before a save request is <a href="#TriggeringSaveRequest">triggered</a>.
609 *
610 * <p>Typically used to avoid displaying the save UI for values that are autofilled but
611 * reformattedby the app. For example, to remove spaces between every 4 digits of a
612 * credit card number:
613 *
614 * <pre class="prettyprint">
Felipe Leme601d2202017-11-17 08:21:23 -0800615 * builder.addSanitizer(new TextValueSanitizer(
616 * Pattern.compile("^(\\d{4})\\s?(\\d{4})\\s?(\\d{4})\\s?(\\d{4})$", "$1$2$3$4")),
617 * ccNumberId);
Felipe Lemecd2969c2017-10-05 08:56:04 -0700618 * </pre>
619 *
620 * <p>The same sanitizer can be reused to sanitize multiple fields. For example, to trim
621 * both the username and password fields:
622 *
623 * <pre class="prettyprint">
624 * builder.addSanitizer(
625 * new TextValueSanitizer(Pattern.compile("^\\s*(.*)\\s*$"), "$1"),
626 * usernameId, passwordId);
627 * </pre>
628 *
Felipe Lemef5059c32017-11-17 13:54:55 -0800629 * <p>The sanitizer can also be used as an alternative for a
Felipe Leme601d2202017-11-17 08:21:23 -0800630 * {@link #setValidator(Validator) validator}. If any of the {@code ids} is a
631 * {@link #SaveInfo.Builder(int, AutofillId[]) required id} and the {@code sanitizer} fails
632 * because of it, then the save UI is not shown.
Felipe Lemef5059c32017-11-17 13:54:55 -0800633 *
Felipe Lemecd2969c2017-10-05 08:56:04 -0700634 * @param sanitizer an implementation provided by the Android System.
635 * @param ids id of fields whose value will be sanitized.
636 * @return this builder.
637 *
638 * @throws IllegalArgumentException if a sanitizer for any of the {@code ids} has already
639 * been added or if {@code ids} is empty.
640 */
641 public @NonNull Builder addSanitizer(@NonNull Sanitizer sanitizer,
642 @NonNull AutofillId... ids) {
643 throwIfDestroyed();
644 Preconditions.checkArgument(!ArrayUtils.isEmpty(ids), "ids cannot be empty or null");
645 Preconditions.checkArgument((sanitizer instanceof InternalSanitizer),
646 "not provided by Android System: " + sanitizer);
647
648 if (mSanitizers == null) {
649 mSanitizers = new ArrayMap<>();
650 mSanitizerIds = new ArraySet<>(ids.length);
651 }
652
653 // Check for duplicates first.
654 for (AutofillId id : ids) {
655 Preconditions.checkArgument(!mSanitizerIds.contains(id), "already added %s", id);
656 mSanitizerIds.add(id);
657 }
658
659 mSanitizers.put((InternalSanitizer) sanitizer, ids);
660
661 return this;
662 }
663
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700664 /**
665 * Explicitly defines the view that should commit the autofill context when clicked.
666 *
667 * <p>Usually, the save request is only automatically
668 * <a href="#TriggeringSaveRequest">triggered</a> after the activity is
669 * finished or all relevant views become invisible, but there are scenarios where the
670 * autofill context is automatically commited too late
671 * &mdash;for example, when the activity manually clears the autofillable views when a
672 * button is tapped. This method can be used to trigger the autofill save UI earlier in
673 * these scenarios.
674 *
675 * <p><b>Note:</b> This method should only be used in scenarios where the automatic workflow
676 * is not enough, otherwise it could trigger the autofill save UI when it should not&mdash;
677 * for example, when the user entered invalid credentials for the autofillable views.
678 */
679 public @NonNull Builder setTriggerId(@NonNull AutofillId id) {
680 throwIfDestroyed();
681 mTriggerId = Preconditions.checkNotNull(id);
682 return this;
683 }
684
Felipe Lemecd2969c2017-10-05 08:56:04 -0700685 /**
Felipe Lemef69761f2017-02-23 17:52:01 -0800686 * Builds a new {@link SaveInfo} instance.
Felipe Leme7fc29dd2017-07-17 11:38:40 -0700687 *
688 * @throws IllegalStateException if no
Felipe Leme8d7f7f42018-07-31 16:14:39 -0700689 * {@link #SaveInfo.Builder(int, AutofillId[]) required ids},
690 * or {@link #setOptionalIds(AutofillId[]) optional ids}, or {@link #FLAG_DELAY_SAVE}
691 * were set
Felipe Lemef69761f2017-02-23 17:52:01 -0800692 */
693 public SaveInfo build() {
694 throwIfDestroyed();
Felipe Leme7fc29dd2017-07-17 11:38:40 -0700695 Preconditions.checkState(
Felipe Leme8d7f7f42018-07-31 16:14:39 -0700696 !ArrayUtils.isEmpty(mRequiredIds) || !ArrayUtils.isEmpty(mOptionalIds)
697 || (mFlags & FLAG_DELAY_SAVE) != 0,
698 "must have at least one required or optional id or FLAG_DELAYED_SAVE");
Felipe Lemef69761f2017-02-23 17:52:01 -0800699 mDestroyed = true;
700 return new SaveInfo(this);
701 }
702
703 private void throwIfDestroyed() {
704 if (mDestroyed) {
705 throw new IllegalStateException("Already called #build()");
706 }
707 }
Felipe Lemef69761f2017-02-23 17:52:01 -0800708 }
709
710 /////////////////////////////////////
711 // Object "contract" methods. //
712 /////////////////////////////////////
713 @Override
714 public String toString() {
Felipe Leme9f9ee252017-04-27 13:56:22 -0700715 if (!sDebug) return super.toString();
Felipe Lemef69761f2017-02-23 17:52:01 -0800716
Felipe Lemeda9ea342018-03-22 15:19:51 -0700717 final StringBuilder builder = new StringBuilder("SaveInfo: [type=")
Felipe Leme7d5adb52017-04-06 17:57:43 -0700718 .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType))
Felipe Leme82e37932017-03-10 10:05:56 -0800719 .append(", requiredIds=").append(Arrays.toString(mRequiredIds))
Felipe Lemeda9ea342018-03-22 15:19:51 -0700720 .append(", style=").append(DebugUtils.flagsToString(SaveInfo.class,
721 "NEGATIVE_BUTTON_STYLE_", mNegativeButtonStyle));
722 if (mOptionalIds != null) {
723 builder.append(", optionalIds=").append(Arrays.toString(mOptionalIds));
724 }
725 if (mDescription != null) {
726 builder.append(", description=").append(mDescription);
727 }
728 if (mFlags != 0) {
729 builder.append(", flags=").append(mFlags);
730 }
731 if (mCustomDescription != null) {
732 builder.append(", customDescription=").append(mCustomDescription);
733 }
734 if (mValidator != null) {
735 builder.append(", validator=").append(mValidator);
736 }
737 if (mSanitizerKeys != null) {
738 builder.append(", sanitizerKeys=").append(mSanitizerKeys.length);
739 }
740 if (mSanitizerValues != null) {
741 builder.append(", sanitizerValues=").append(mSanitizerValues.length);
742 }
743 if (mTriggerId != null) {
744 builder.append(", triggerId=").append(mTriggerId);
745 }
746
747 return builder.append("]").toString();
Felipe Lemef69761f2017-02-23 17:52:01 -0800748 }
749
750 /////////////////////////////////////
751 // Parcelable "contract" methods. //
752 /////////////////////////////////////
753
754 @Override
755 public int describeContents() {
756 return 0;
757 }
758
759 @Override
760 public void writeToParcel(Parcel parcel, int flags) {
761 parcel.writeInt(mType);
Felipe Leme82e37932017-03-10 10:05:56 -0800762 parcel.writeParcelableArray(mRequiredIds, flags);
Felipe Leme7fc29dd2017-07-17 11:38:40 -0700763 parcel.writeParcelableArray(mOptionalIds, flags);
Svetoslav Ganovdc6cccb2017-04-24 18:11:27 -0700764 parcel.writeInt(mNegativeButtonStyle);
Svet Ganov33d06fc2017-03-01 10:38:34 -0800765 parcel.writeParcelable(mNegativeActionListener, flags);
Felipe Lemeb72f0122017-02-24 12:53:27 -0800766 parcel.writeCharSequence(mDescription);
Felipe Leme979013d2017-06-22 10:59:23 -0700767 parcel.writeParcelable(mCustomDescription, flags);
768 parcel.writeParcelable(mValidator, flags);
Felipe Lemecd2969c2017-10-05 08:56:04 -0700769 parcel.writeParcelableArray(mSanitizerKeys, flags);
770 if (mSanitizerKeys != null) {
771 for (int i = 0; i < mSanitizerValues.length; i++) {
772 parcel.writeParcelableArray(mSanitizerValues[i], flags);
773 }
774 }
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700775 parcel.writeParcelable(mTriggerId, flags);
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700776 parcel.writeInt(mFlags);
Felipe Lemef69761f2017-02-23 17:52:01 -0800777 }
778
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700779 public static final @android.annotation.NonNull Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
Felipe Lemef69761f2017-02-23 17:52:01 -0800780 @Override
781 public SaveInfo createFromParcel(Parcel parcel) {
Felipe Leme7fc29dd2017-07-17 11:38:40 -0700782
Felipe Lemef69761f2017-02-23 17:52:01 -0800783 // Always go through the builder to ensure the data ingested by
784 // the system obeys the contract of the builder to avoid attacks
785 // using specially crafted parcels.
Felipe Leme7fc29dd2017-07-17 11:38:40 -0700786 final int type = parcel.readInt();
787 final AutofillId[] requiredIds = parcel.readParcelableArray(null, AutofillId.class);
788 final Builder builder = requiredIds != null
789 ? new Builder(type, requiredIds)
790 : new Builder(type);
Felipe Leme18d0ef72017-06-15 15:29:23 -0700791 final AutofillId[] optionalIds = parcel.readParcelableArray(null, AutofillId.class);
792 if (optionalIds != null) {
793 builder.setOptionalIds(optionalIds);
794 }
Felipe Leme7fc29dd2017-07-17 11:38:40 -0700795
796 builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
Felipe Lemeb72f0122017-02-24 12:53:27 -0800797 builder.setDescription(parcel.readCharSequence());
Felipe Leme979013d2017-06-22 10:59:23 -0700798 final CustomDescription customDescripton = parcel.readParcelable(null);
799 if (customDescripton != null) {
800 builder.setCustomDescription(customDescripton);
801 }
802 final InternalValidator validator = parcel.readParcelable(null);
803 if (validator != null) {
804 builder.setValidator(validator);
805 }
Felipe Lemecd2969c2017-10-05 08:56:04 -0700806 final InternalSanitizer[] sanitizers =
807 parcel.readParcelableArray(null, InternalSanitizer.class);
808 if (sanitizers != null) {
809 final int size = sanitizers.length;
810 for (int i = 0; i < size; i++) {
811 final AutofillId[] autofillIds =
812 parcel.readParcelableArray(null, AutofillId.class);
813 builder.addSanitizer(sanitizers[i], autofillIds);
814 }
815 }
Felipe Leme2fe3ade2017-09-28 15:03:36 -0700816 final AutofillId triggerId = parcel.readParcelable(null);
817 if (triggerId != null) {
818 builder.setTriggerId(triggerId);
819 }
Philip P. Moltmann9023d142017-04-17 17:11:01 -0700820 builder.setFlags(parcel.readInt());
Felipe Lemef69761f2017-02-23 17:52:01 -0800821 return builder.build();
822 }
823
824 @Override
825 public SaveInfo[] newArray(int size) {
826 return new SaveInfo[size];
827 }
828 };
829}