• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.service.autofill;
18 
19 import static android.view.autofill.Helper.sDebug;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SuppressLint;
24 import android.annotation.SystemApi;
25 import android.annotation.TestApi;
26 import android.content.ClipData;
27 import android.content.IntentSender;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.view.autofill.AutofillId;
31 import android.view.autofill.AutofillValue;
32 import android.widget.RemoteViews;
33 
34 import com.android.internal.util.Preconditions;
35 
36 import java.util.ArrayList;
37 import java.util.Objects;
38 import java.util.regex.Pattern;
39 
40 /**
41  * <p>A <code>Dataset</code> object represents a group of fields (key / value pairs) used
42  * to autofill parts of a screen.
43  *
44  * <p>For more information about the role of datasets in the autofill workflow, read
45  * <a href="/guide/topics/text/autofill-services">Build autofill services</a> and the
46  * <code><a href="/reference/android/service/autofill/AutofillService">AutofillService</a></code>
47  * documentation.
48  *
49  * <a name="BasicUsage"></a>
50  * <h3>Basic usage</h3>
51  *
52  * <p>In its simplest form, a dataset contains one or more fields (comprised of
53  * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter
54  * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields
55  * (each field could have its own {@link RemoteViews presentation}, or use the default
56  * {@link RemoteViews presentation} associated with the whole dataset).
57  *
58  * <p>When an autofill service returns datasets in a {@link FillResponse}
59  * and the screen input is focused in a view that is present in at least one of these datasets,
60  * the Android System displays a UI containing the {@link RemoteViews presentation} of
61  * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
62  * dataset from the UI, all views in that dataset are autofilled.
63  *
64  * <p>If both the current Input Method and autofill service supports inline suggestions, the Dataset
65  * can be shown by the keyboard as a suggestion. To use this feature, the Dataset should contain
66  * an {@link InlinePresentation} representing how the inline suggestion UI will be rendered.
67  *
68  * <a name="FillDialogUI"></a>
69  * <h3>Fill Dialog UI</h3>
70  *
71  * <p>The fill dialog UI is a more conspicuous and efficient interface than dropdown UI. If autofill
72  * suggestions are available when the user clicks on a field that supports filling the dialog UI,
73  * Autofill will pop up a fill dialog. The dialog will take up a larger area to display the
74  * datasets, so it is easy for users to pay attention to the datasets and selecting a dataset.
75  * If the user focuses on the view before suggestions are available, will fall back to dropdown UI
76  * or inline suggestions.
77  *
78  * <a name="Authentication"></a>
79  * <h3>Dataset authentication</h3>
80  *
81  * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates
82  * the dataset&mdash;in that case, when a dataset is selected by the user, the Android System
83  * launches an intent set by the service to "unlock" the dataset.
84  *
85  * <p>For example, when a data set contains credit card information (such as number,
86  * expiration date, and verification code), you could provide a dataset presentation saying
87  * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking
88  * the user to enter the credit card code, and if the user enters a valid code, you could then
89  * "unlock" the dataset.
90  *
91  * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example,
92  * if the activity being autofilled is an account creation screen, you could use an authenticated
93  * dataset to automatically generate a random password for the user.
94  *
95  * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset
96  * authentication mechanism.
97  *
98  * <a name="Filtering"></a>
99  * <h3>Filtering</h3>
100  * <p>The autofill UI automatically changes which values are shown based on value of the view
101  * anchoring it, following the rules below:
102  * <ol>
103  *   <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not
104  * {@link AutofillValue#isText() text} or is empty, all datasets are shown.
105  *   <li>Datasets that have a filter regex (set through {@link Field.Builder#setFilter(Pattern)}
106  *   and {@link Dataset.Builder#setField(AutofillId, Field)}) and whose regex matches the view's
107  *   text value converted to lower case are shown.
108  *   <li>Datasets that do not require authentication, have a field value that is
109  * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts
110  * with the lower case value of the view's text are shown.
111  *   <li>All other datasets are hidden.
112  * </ol>
113  */
114 public final class Dataset implements Parcelable {
115 
116     private final ArrayList<AutofillId> mFieldIds;
117     private final ArrayList<AutofillValue> mFieldValues;
118     private final ArrayList<RemoteViews> mFieldPresentations;
119     private final ArrayList<RemoteViews> mFieldDialogPresentations;
120     private final ArrayList<InlinePresentation> mFieldInlinePresentations;
121     private final ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
122     private final ArrayList<DatasetFieldFilter> mFieldFilters;
123     @Nullable private final ClipData mFieldContent;
124     private final RemoteViews mPresentation;
125     private final RemoteViews mDialogPresentation;
126     @Nullable private final InlinePresentation mInlinePresentation;
127     @Nullable private final InlinePresentation mInlineTooltipPresentation;
128     private final IntentSender mAuthentication;
129     @Nullable String mId;
130 
Dataset(Builder builder)131     private Dataset(Builder builder) {
132         mFieldIds = builder.mFieldIds;
133         mFieldValues = builder.mFieldValues;
134         mFieldPresentations = builder.mFieldPresentations;
135         mFieldDialogPresentations = builder.mFieldDialogPresentations;
136         mFieldInlinePresentations = builder.mFieldInlinePresentations;
137         mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations;
138         mFieldFilters = builder.mFieldFilters;
139         mFieldContent = builder.mFieldContent;
140         mPresentation = builder.mPresentation;
141         mDialogPresentation = builder.mDialogPresentation;
142         mInlinePresentation = builder.mInlinePresentation;
143         mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
144         mAuthentication = builder.mAuthentication;
145         mId = builder.mId;
146     }
147 
148     /** @hide */
149     @TestApi
150     @SuppressLint({"ConcreteCollection", "NullableCollection"})
getFieldIds()151     public @Nullable ArrayList<AutofillId> getFieldIds() {
152         return mFieldIds;
153     }
154 
155     /** @hide */
156     @TestApi
157     @SuppressLint({"ConcreteCollection", "NullableCollection"})
getFieldValues()158     public @Nullable ArrayList<AutofillValue> getFieldValues() {
159         return mFieldValues;
160     }
161 
162     /** @hide */
getFieldPresentation(int index)163     public RemoteViews getFieldPresentation(int index) {
164         final RemoteViews customPresentation = mFieldPresentations.get(index);
165         return customPresentation != null ? customPresentation : mPresentation;
166     }
167 
168     /** @hide */
getFieldDialogPresentation(int index)169     public RemoteViews getFieldDialogPresentation(int index) {
170         final RemoteViews customPresentation = mFieldDialogPresentations.get(index);
171         return customPresentation != null ? customPresentation : mDialogPresentation;
172     }
173 
174     /** @hide */
getFieldInlinePresentation(int index)175     public @Nullable InlinePresentation getFieldInlinePresentation(int index) {
176         final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(index);
177         return inlinePresentation != null ? inlinePresentation : mInlinePresentation;
178     }
179 
180     /** @hide */
getFieldInlineTooltipPresentation(int index)181     public @Nullable InlinePresentation getFieldInlineTooltipPresentation(int index) {
182         final InlinePresentation inlineTooltipPresentation =
183                 mFieldInlineTooltipPresentations.get(index);
184         return inlineTooltipPresentation != null
185                 ? inlineTooltipPresentation : mInlineTooltipPresentation;
186     }
187 
188     /** @hide */
getFilter(int index)189     public @Nullable DatasetFieldFilter getFilter(int index) {
190         return mFieldFilters.get(index);
191     }
192 
193     /**
194      * Returns the content to be filled for a non-text suggestion. This is only applicable to
195      * augmented autofill. The target field for the content is available via {@link #getFieldIds()}
196      * (guaranteed to have a single field id set when the return value here is non-null). See
197      * {@link Builder#setContent(AutofillId, ClipData)} for more info.
198      *
199      * @hide
200      */
201     @TestApi
getFieldContent()202     public @Nullable ClipData getFieldContent() {
203         return mFieldContent;
204     }
205 
206     /** @hide */
207     @TestApi
getAuthentication()208     public @Nullable IntentSender getAuthentication() {
209         return mAuthentication;
210     }
211 
212     /** @hide */
213     @TestApi
isEmpty()214     public boolean isEmpty() {
215         return mFieldIds == null || mFieldIds.isEmpty();
216     }
217 
218     @Override
toString()219     public String toString() {
220         if (!sDebug) return super.toString();
221 
222         final StringBuilder builder = new StringBuilder("Dataset[");
223         if (mId == null) {
224             builder.append("noId");
225         } else {
226             // Cannot disclose id because it could contain PII.
227             builder.append("id=").append(mId.length()).append("_chars");
228         }
229         if (mFieldIds != null) {
230             builder.append(", fieldIds=").append(mFieldIds);
231         }
232         if (mFieldValues != null) {
233             builder.append(", fieldValues=").append(mFieldValues);
234         }
235         if (mFieldContent != null) {
236             builder.append(", fieldContent=").append(mFieldContent);
237         }
238         if (mFieldPresentations != null) {
239             builder.append(", fieldPresentations=").append(mFieldPresentations.size());
240         }
241         if (mFieldDialogPresentations != null) {
242             builder.append(", fieldDialogPresentations=").append(mFieldDialogPresentations.size());
243         }
244         if (mFieldInlinePresentations != null) {
245             builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size());
246         }
247         if (mFieldInlineTooltipPresentations != null) {
248             builder.append(", fieldInlineTooltipInlinePresentations=").append(
249                     mFieldInlineTooltipPresentations.size());
250         }
251         if (mFieldFilters != null) {
252             builder.append(", fieldFilters=").append(mFieldFilters.size());
253         }
254         if (mPresentation != null) {
255             builder.append(", hasPresentation");
256         }
257         if (mDialogPresentation != null) {
258             builder.append(", hasDialogPresentation");
259         }
260         if (mInlinePresentation != null) {
261             builder.append(", hasInlinePresentation");
262         }
263         if (mInlineTooltipPresentation != null) {
264             builder.append(", hasInlineTooltipPresentation");
265         }
266         if (mAuthentication != null) {
267             builder.append(", hasAuthentication");
268         }
269         return builder.append(']').toString();
270     }
271 
272     /**
273      * Gets the id of this dataset.
274      *
275      * @return The id of this dataset or {@code null} if not set
276      *
277      * @hide
278      */
279     @TestApi
getId()280     public @Nullable String getId() {
281         return mId;
282     }
283 
284     /**
285      * A builder for {@link Dataset} objects. You must provide at least
286      * one value for a field or set an authentication intent.
287      */
288     public static final class Builder {
289         private ArrayList<AutofillId> mFieldIds;
290         private ArrayList<AutofillValue> mFieldValues;
291         private ArrayList<RemoteViews> mFieldPresentations;
292         private ArrayList<RemoteViews> mFieldDialogPresentations;
293         private ArrayList<InlinePresentation> mFieldInlinePresentations;
294         private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
295         private ArrayList<DatasetFieldFilter> mFieldFilters;
296         @Nullable private ClipData mFieldContent;
297         private RemoteViews mPresentation;
298         private RemoteViews mDialogPresentation;
299         @Nullable private InlinePresentation mInlinePresentation;
300         @Nullable private InlinePresentation mInlineTooltipPresentation;
301         private IntentSender mAuthentication;
302         private boolean mDestroyed;
303         @Nullable private String mId;
304 
305         /**
306          * Creates a new builder.
307          *
308          * @param presentation The presentation used to visualize this dataset.
309          * @deprecated Use {@link #Builder(Presentations)} instead.
310          */
311         @Deprecated
Builder(@onNull RemoteViews presentation)312         public Builder(@NonNull RemoteViews presentation) {
313             Objects.requireNonNull(presentation, "presentation must be non-null");
314             mPresentation = presentation;
315         }
316 
317         /**
318          * Creates a new builder.
319          *
320          * <p>Only called by augmented autofill.
321          *
322          * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
323          *              as inline suggestions. If the dataset supports inline suggestions,
324          *              this should not be null.
325          * @hide
326          * @deprecated Use {@link #Builder(Presentations)} instead.
327          */
328         @SystemApi
329         @Deprecated
Builder(@onNull InlinePresentation inlinePresentation)330         public Builder(@NonNull InlinePresentation inlinePresentation) {
331             Objects.requireNonNull(inlinePresentation, "inlinePresentation must be non-null");
332             mInlinePresentation = inlinePresentation;
333         }
334 
335         /**
336          * Creates a new builder.
337          *
338          * @param presentations The presentations used to visualize this dataset.
339          */
Builder(@onNull Presentations presentations)340         public Builder(@NonNull Presentations presentations) {
341             Objects.requireNonNull(presentations, "presentations must be non-null");
342 
343             mPresentation = presentations.getMenuPresentation();
344             mInlinePresentation = presentations.getInlinePresentation();
345             mInlineTooltipPresentation = presentations.getInlineTooltipPresentation();
346             mDialogPresentation = presentations.getDialogPresentation();
347         }
348 
349         /**
350          * Creates a new builder for a dataset where each field will be visualized independently.
351          *
352          * <p>When using this constructor, a presentation must be provided for each field through
353          * {@link #setField(AutofillId, Field)}.
354          */
Builder()355         public Builder() {
356         }
357 
358         /**
359          * Sets the {@link InlinePresentation} used to visualize this dataset as inline suggestions.
360          * If the dataset supports inline suggestions this should not be null.
361          *
362          * @throws IllegalStateException if {@link #build()} was already called.
363          *
364          * @return this builder.
365          * @deprecated Use {@link #Builder(Presentations)} instead.
366          */
367         @Deprecated
setInlinePresentation( @onNull InlinePresentation inlinePresentation)368         public @NonNull Builder setInlinePresentation(
369                 @NonNull InlinePresentation inlinePresentation) {
370             throwIfDestroyed();
371             Objects.requireNonNull(inlinePresentation, "inlinePresentation must be non-null");
372             mInlinePresentation = inlinePresentation;
373             return this;
374         }
375 
376         /**
377          * Visualizes this dataset as inline suggestions.
378          *
379          * @param inlinePresentation the {@link InlinePresentation} used to visualize this
380          *         dataset as inline suggestions. If the dataset supports inline suggestions this
381          *         should not be null.
382          * @param inlineTooltipPresentation the {@link InlinePresentation} used to show
383          *         the tooltip for the {@code inlinePresentation}.
384          *
385          * @throws IllegalStateException if {@link #build()} was already called.
386          *
387          * @return this builder.
388          * @deprecated Use {@link #Builder(Presentations)} instead.
389          */
390         @Deprecated
setInlinePresentation( @onNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation)391         public @NonNull Builder setInlinePresentation(
392                 @NonNull InlinePresentation inlinePresentation,
393                 @NonNull InlinePresentation inlineTooltipPresentation) {
394             throwIfDestroyed();
395             Objects.requireNonNull(inlinePresentation, "inlinePresentation must be non-null");
396             Objects.requireNonNull(inlineTooltipPresentation,
397                     "inlineTooltipPresentation must be non-null");
398             mInlinePresentation = inlinePresentation;
399             mInlineTooltipPresentation = inlineTooltipPresentation;
400             return this;
401         }
402 
403         /**
404          * Triggers a custom UI before before autofilling the screen with the contents of this
405          * dataset.
406          *
407          * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
408          * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
409          * for examples.
410          *
411          * <p>This method is called when you need to provide an authentication
412          * UI for the data set. For example, when a data set contains credit card information
413          * (such as number, expiration date, and verification code), you can display UI
414          * asking for the verification code before filing in the data. Even if the
415          * data set is completely populated the system will launch the specified authentication
416          * intent and will need your approval to fill it in. Since the data set is "locked"
417          * until the user authenticates it, typically this data set name is masked
418          * (for example, "VISA....1234"). Typically you would want to store the data set
419          * labels non-encrypted and the actual sensitive data encrypted and not in memory.
420          * This allows showing the labels in the UI while involving the user if one of
421          * the items with these labels is chosen. Note that if you use sensitive data as
422          * a label, for example an email address, then it should also be encrypted.</p>
423          *
424          * <p>When a user triggers autofill, the system launches the provided intent
425          * whose extras will have the {@link
426          * android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen content},
427          * and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE client
428          * state}. Once you complete your authentication flow you should set the activity
429          * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
430          * {@link Dataset dataset} or a fully-populated {@link FillResponse response} by
431          * setting it to the {@link
432          * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you
433          * provide a dataset in the result, it will replace the authenticated dataset and
434          * will be immediately filled in. An exception to this behavior is if the original
435          * dataset represents a pinned inline suggestion (i.e. any of the field in the dataset
436          * has a pinned inline presentation, see {@link InlinePresentation#isPinned()}), then
437          * the original dataset will not be replaced,
438          * so that it can be triggered as a pending intent again.
439          * If you provide a response, it will replace the
440          * current response and the UI will be refreshed. For example, if you provided
441          * credit card information without the CVV for the data set in the {@link FillResponse
442          * response} then the returned data set should contain the CVV entry.
443          *
444          * <p><b>Note:</b> Do not make the provided pending intent
445          * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
446          * platform needs to fill in the authentication arguments.
447          *
448          * @param authentication Intent to an activity with your authentication flow.
449          *
450          * @throws IllegalStateException if {@link #build()} was already called.
451          *
452          * @return this builder.
453          *
454          * @see android.app.PendingIntent
455          */
setAuthentication(@ullable IntentSender authentication)456         public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) {
457             throwIfDestroyed();
458             mAuthentication = authentication;
459             return this;
460         }
461 
462         /**
463          * Sets the id for the dataset so its usage can be tracked.
464          *
465          * <p>Dataset usage can be tracked for 2 purposes:
466          *
467          * <ul>
468          *   <li>For statistical purposes, the service can call
469          * {@link AutofillService#getFillEventHistory()} when handling {@link
470          * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
471          * calls.
472          *   <li>For normal autofill workflow, the service can call
473          *   {@link SaveRequest#getDatasetIds()} when handling
474          *   {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} calls.
475          * </ul>
476          *
477          * @param id id for this dataset or {@code null} to unset.
478          *
479          * @throws IllegalStateException if {@link #build()} was already called.
480          *
481          * @return this builder.
482          */
setId(@ullable String id)483         public @NonNull Builder setId(@Nullable String id) {
484             throwIfDestroyed();
485             mId = id;
486             return this;
487         }
488 
489         /**
490          * Sets the content for a field.
491          *
492          * <p>Only called by augmented autofill.
493          *
494          * <p>For a given field, either a {@link AutofillValue value} or content can be filled, but
495          * not both. Furthermore, when filling content, only a single field can be filled.
496          *
497          * <p>The provided {@link ClipData} can contain content URIs (e.g. a URI for an image).
498          * The augmented autofill provider setting the content here must itself have at least
499          * read permissions to any passed content URIs. If the user accepts the suggestion backed
500          * by the content URI(s), the platform will automatically grant read URI permissions to
501          * the app being autofilled, just before passing the content URI(s) to it. The granted
502          * permissions will be transient and tied to the lifecycle of the activity being filled
503          * (when the activity finishes, permissions will automatically be revoked by the platform).
504          *
505          * @param id id returned by
506          * {@link android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
507          * @param content content to be autofilled. Pass {@code null} if you do not have the content
508          * but the target view is a logical part of the dataset. For example, if the dataset needs
509          * authentication.
510          *
511          * @throws IllegalStateException if {@link #build()} was already called.
512          * @throws IllegalArgumentException if the provided content
513          * {@link ClipData.Item#getIntent() contains an intent}
514          *
515          * @return this builder.
516          *
517          * @hide
518          */
519         @TestApi
520         @SystemApi
521         @SuppressLint("MissingGetterMatchingBuilder")
setContent(@onNull AutofillId id, @Nullable ClipData content)522         public @NonNull Builder setContent(@NonNull AutofillId id, @Nullable ClipData content) {
523             throwIfDestroyed();
524             if (content != null) {
525                 for (int i = 0; i < content.getItemCount(); i++) {
526                     Preconditions.checkArgument(content.getItemAt(i).getIntent() == null,
527                             "Content items cannot contain an Intent: content=" + content);
528                 }
529             }
530             setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
531             mFieldContent = content;
532             return this;
533         }
534 
535         /**
536          * Sets the value of a field.
537          *
538          * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would
539          * throw an {@link IllegalStateException} if this builder was constructed without a
540          * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and
541          * higher removed this restriction because datasets used as an
542          * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT
543          * authentication result} do not need a presentation. But if you don't set the presentation
544          * in the constructor in a dataset that is meant to be shown to the user, the autofill UI
545          * for this field will not be displayed.
546          *
547          * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and
548          * higher, datasets that require authentication can be also be filtered by passing a
549          * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter.
550          *
551          * @param id id returned by {@link
552          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
553          * @param value value to be autofilled. Pass {@code null} if you do not have the value
554          *        but the target view is a logical part of the dataset. For example, if
555          *        the dataset needs authentication and you have no access to the value.
556          *
557          * @throws IllegalStateException if {@link #build()} was already called.
558          *
559          * @return this builder.
560          * @deprecated Use {@link #setField(AutofillId, Field)} instead.
561          */
562         @Deprecated
setValue(@onNull AutofillId id, @Nullable AutofillValue value)563         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
564             throwIfDestroyed();
565             setLifeTheUniverseAndEverything(id, value, null, null, null, null, null);
566             return this;
567         }
568 
569         /**
570          * Sets the value of a field, using a custom {@link RemoteViews presentation} to
571          * visualize it.
572          *
573          * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and
574          * higher, datasets that require authentication can be also be filtered by passing a
575          * {@link AutofillValue#forText(CharSequence) text value} as the  {@code value} parameter.
576          *
577          * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
578          * or background color: Autofill on different platforms may have different themes.
579          *
580          * @param id id returned by {@link
581          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
582          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
583          *        but the target view is a logical part of the dataset. For example, if
584          *        the dataset needs authentication and you have no access to the value.
585          * @param presentation the presentation used to visualize this field.
586          *
587          * @throws IllegalStateException if {@link #build()} was already called.
588          *
589          * @return this builder.
590          * @deprecated Use {@link #setField(AutofillId, Field)} instead.
591          */
592         @Deprecated
setValue(@onNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation)593         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
594                 @NonNull RemoteViews presentation) {
595             throwIfDestroyed();
596             Objects.requireNonNull(presentation, "presentation cannot be null");
597             setLifeTheUniverseAndEverything(id, value, presentation, null, null, null, null);
598             return this;
599         }
600 
601         /**
602          * Sets the value of a field using an <a href="#Filtering">explicit filter</a>.
603          *
604          * <p>This method is typically used when the dataset requires authentication and the service
605          * does not know its value but wants to hide the dataset after the user enters a minimum
606          * number of characters. For example, if the dataset represents a credit card number and the
607          * service does not want to show the "Tap to authenticate" message until the user tapped
608          * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
609          *
610          * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
611          * value it's easier to filter by calling {@link #setValue(AutofillId, AutofillValue)} and
612          * use the value to filter.
613          *
614          * @param id id returned by {@link
615          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
616          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
617          *        but the target view is a logical part of the dataset. For example, if
618          *        the dataset needs authentication and you have no access to the value.
619          * @param filter regex used to determine if the dataset should be shown in the autofill UI;
620          *        when {@code null}, it disables filtering on that dataset (this is the recommended
621          *        approach when {@code value} is not {@code null} and field contains sensitive data
622          *        such as passwords).
623          *
624          * @return this builder.
625          * @throws IllegalStateException if the builder was constructed without a
626          *         {@link RemoteViews presentation} or {@link #build()} was already called.
627          * @deprecated Use {@link #setField(AutofillId, Field)} instead.
628          */
629         @Deprecated
setValue(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter)630         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
631                 @Nullable Pattern filter) {
632             throwIfDestroyed();
633             Preconditions.checkState(mPresentation != null,
634                     "Dataset presentation not set on constructor");
635             setLifeTheUniverseAndEverything(
636                     id, value, null, null, null, new DatasetFieldFilter(filter), null);
637             return this;
638         }
639 
640         /**
641          * Sets the value of a field, using a custom {@link RemoteViews presentation} to
642          * visualize it and a <a href="#Filtering">explicit filter</a>.
643          *
644          * <p>This method is typically used when the dataset requires authentication and the service
645          * does not know its value but wants to hide the dataset after the user enters a minimum
646          * number of characters. For example, if the dataset represents a credit card number and the
647          * service does not want to show the "Tap to authenticate" message until the user tapped
648          * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
649          *
650          * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
651          * value it's easier to filter by calling
652          * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
653          *
654          * @param id id returned by {@link
655          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
656          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
657          *        but the target view is a logical part of the dataset. For example, if
658          *        the dataset needs authentication and you have no access to the value.
659          * @param filter regex used to determine if the dataset should be shown in the autofill UI;
660          *        when {@code null}, it disables filtering on that dataset (this is the recommended
661          *        approach when {@code value} is not {@code null} and field contains sensitive data
662          *        such as passwords).
663          * @param presentation the presentation used to visualize this field.
664          *
665          * @throws IllegalStateException if {@link #build()} was already called.
666          *
667          * @return this builder.
668          * @deprecated Use {@link #setField(AutofillId, Field)} instead.
669          */
670         @Deprecated
setValue(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation)671         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
672                 @Nullable Pattern filter, @NonNull RemoteViews presentation) {
673             throwIfDestroyed();
674             Objects.requireNonNull(presentation, "presentation cannot be null");
675             setLifeTheUniverseAndEverything(id, value, presentation, null, null,
676                     new DatasetFieldFilter(filter), null);
677             return this;
678         }
679 
680         /**
681          * Sets the value of a field, using a custom {@link RemoteViews presentation} to
682          * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion.
683          *
684          * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
685          * value it's easier to filter by calling
686          * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
687          *
688          * @param id id returned by {@link
689          *        android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
690          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
691          *        but the target view is a logical part of the dataset. For example, if
692          *        the dataset needs authentication and you have no access to the value.
693          * @param presentation the presentation used to visualize this field.
694          * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
695          *        as inline suggestions. If the dataset supports inline suggestions,
696          *        this should not be null.
697          *
698          * @throws IllegalStateException if {@link #build()} was already called.
699          *
700          * @return this builder.
701          * @deprecated Use {@link #setField(AutofillId, Field)} instead.
702          */
703         @Deprecated
setValue(@onNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation)704         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
705                 @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) {
706             throwIfDestroyed();
707             Objects.requireNonNull(presentation, "presentation cannot be null");
708             Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
709             setLifeTheUniverseAndEverything(
710                     id, value, presentation, inlinePresentation, null, null, null);
711             return this;
712         }
713 
714         /**
715          * Sets the value of a field, using a custom {@link RemoteViews presentation} to
716          * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion.
717          *
718          * @see #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation)
719          *
720          * @param id id returned by {@link
721          *        android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
722          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
723          *        but the target view is a logical part of the dataset. For example, if
724          *        the dataset needs authentication and you have no access to the value.
725          * @param presentation the presentation used to visualize this field.
726          * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
727          *        as inline suggestions. If the dataset supports inline suggestions,
728          *        this should not be null.
729          * @param inlineTooltipPresentation The {@link InlinePresentation} used to show
730          *        the tooltip for the {@code inlinePresentation}.
731          *
732          * @throws IllegalStateException if {@link #build()} was already called.
733          *
734          * @return this builder.
735          * @deprecated Use {@link #setField(AutofillId, Field)} instead.
736          */
737         @Deprecated
setValue(@onNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation)738         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
739                 @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation,
740                 @NonNull InlinePresentation inlineTooltipPresentation) {
741             throwIfDestroyed();
742             Objects.requireNonNull(presentation, "presentation cannot be null");
743             Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
744             Objects.requireNonNull(inlineTooltipPresentation,
745                     "inlineTooltipPresentation cannot be null");
746             setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
747                     inlineTooltipPresentation, null, null);
748             return this;
749         }
750 
751         /**
752          * Sets the value of a field, using a custom {@link RemoteViews presentation} to
753          * visualize it and a <a href="#Filtering">explicit filter</a>, and an
754          * {@link InlinePresentation} to visualize it as an inline suggestion.
755          *
756          * <p>This method is typically used when the dataset requires authentication and the service
757          * does not know its value but wants to hide the dataset after the user enters a minimum
758          * number of characters. For example, if the dataset represents a credit card number and the
759          * service does not want to show the "Tap to authenticate" message until the user tapped
760          * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
761          *
762          * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
763          * value it's easier to filter by calling
764          * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
765          *
766          * @param id id returned by {@link
767          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
768          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
769          *        but the target view is a logical part of the dataset. For example, if
770          *        the dataset needs authentication and you have no access to the value.
771          * @param filter regex used to determine if the dataset should be shown in the autofill UI;
772          *        when {@code null}, it disables filtering on that dataset (this is the recommended
773          *        approach when {@code value} is not {@code null} and field contains sensitive data
774          *        such as passwords).
775          * @param presentation the presentation used to visualize this field.
776          * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
777          *        as inline suggestions. If the dataset supports inline suggestions, this
778          *        should not be null.
779          *
780          * @throws IllegalStateException if {@link #build()} was already called.
781          *
782          * @return this builder.
783          * @deprecated Use {@link #setField(AutofillId, Field)} instead.
784          */
785         @Deprecated
setValue(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation)786         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
787                 @Nullable Pattern filter, @NonNull RemoteViews presentation,
788                 @NonNull InlinePresentation inlinePresentation) {
789             throwIfDestroyed();
790             Objects.requireNonNull(presentation, "presentation cannot be null");
791             Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
792             setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null,
793                     new DatasetFieldFilter(filter), null);
794             return this;
795         }
796 
797         /**
798          * Sets the value of a field, using a custom {@link RemoteViews presentation} to
799          * visualize it and a <a href="#Filtering">explicit filter</a>, and an
800          * {@link InlinePresentation} to visualize it as an inline suggestion.
801          *
802          * @see #setValue(AutofillId, AutofillValue, Pattern, RemoteViews, InlinePresentation)
803          *
804          * @param id id returned by {@link
805          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
806          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
807          *        but the target view is a logical part of the dataset. For example, if
808          *        the dataset needs authentication and you have no access to the value.
809          * @param filter regex used to determine if the dataset should be shown in the autofill UI;
810          *        when {@code null}, it disables filtering on that dataset (this is the recommended
811          *        approach when {@code value} is not {@code null} and field contains sensitive data
812          *        such as passwords).
813          * @param presentation the presentation used to visualize this field.
814          * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
815          *        as inline suggestions. If the dataset supports inline suggestions, this
816          *        should not be null.
817          * @param inlineTooltipPresentation The {@link InlinePresentation} used to show
818          *        the tooltip for the {@code inlinePresentation}.
819          *
820          * @throws IllegalStateException if {@link #build()} was already called.
821          *
822          * @return this builder.
823          * @deprecated Use {@link #setField(AutofillId, Field)} instead.
824          */
825         @Deprecated
setValue(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation)826         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
827                 @Nullable Pattern filter, @NonNull RemoteViews presentation,
828                 @NonNull InlinePresentation inlinePresentation,
829                 @NonNull InlinePresentation inlineTooltipPresentation) {
830             throwIfDestroyed();
831             Objects.requireNonNull(presentation, "presentation cannot be null");
832             Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
833             Objects.requireNonNull(inlineTooltipPresentation,
834                     "inlineTooltipPresentation cannot be null");
835             setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
836                     inlineTooltipPresentation, new DatasetFieldFilter(filter), null);
837             return this;
838         }
839 
840         /**
841          * Sets the value of a field.
842          *
843          * Before Android 13, this information could be provided using several overloaded
844          * setValue(...) methods. This method replaces those with a Builder pattern.
845          * For example, in the old workflow, the app sets a field would be:
846          * <pre class="prettyprint">
847          *  Dataset.Builder dataset = new Dataset.Builder();
848          *  if (filter != null) {
849          *      if (presentation != null) {
850          *          if (inlinePresentation != null) {
851          *              dataset.setValue(id, value, filter, presentation, inlinePresentation)
852          *          } else {
853          *              dataset.setValue(id, value, filter, presentation);
854          *          }
855          *      } else {
856          *          dataset.setValue(id, value, filter);
857          *      }
858          *  } else {
859          *      if (presentation != null) {
860          *          if (inlinePresentation != null) {
861          *              dataset.setValue(id, value, presentation, inlinePresentation)
862          *          } else {
863          *              dataset.setValue(id, value, presentation);
864          *          }
865          *      } else {
866          *          dataset.setValue(id, value);
867          *      }
868          *  }
869          *  </pre>
870          * <p>The new workflow would be:
871          * <pre class="prettyprint">
872          * Field.Builder fieldBuilder = new Field.Builder();
873          * if (value != null) {
874          *     fieldBuilder.setValue(value);
875          * }
876          * if (filter != null) {
877          *     fieldBuilder.setFilter(filter);
878          * }
879          * Presentations.Builder presentationsBuilder = new Presentations.Builder();
880          * if (presentation != null) {
881          *     presentationsBuilder.setMenuPresentation(presentation);
882          * }
883          * if (inlinePresentation != null) {
884          *     presentationsBuilder.setInlinePresentation(inlinePresentation);
885          * }
886          * if (dialogPresentation != null) {
887          *     presentationsBuilder.setDialogPresentation(dialogPresentation);
888          * }
889          * fieldBuilder.setPresentations(presentationsBuilder.build());
890          * dataset.setField(id, fieldBuilder.build());
891          * </pre>
892          *
893          * @see Field
894          *
895          * @param id id returned by {@link
896          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
897          * @param field the fill information about the field.
898          *
899          * @throws IllegalStateException if {@link #build()} was already called.
900          *
901          * @return this builder.
902          */
setField(@onNull AutofillId id, @Nullable Field field)903         public @NonNull Builder setField(@NonNull AutofillId id, @Nullable Field field) {
904             throwIfDestroyed();
905             if (field == null) {
906                 setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
907             } else {
908                 final DatasetFieldFilter filter = field.getDatasetFieldFilter();
909                 final Presentations presentations = field.getPresentations();
910                 if (presentations == null) {
911                     setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null,
912                             filter, null);
913                 } else {
914                     setLifeTheUniverseAndEverything(id, field.getValue(),
915                             presentations.getMenuPresentation(),
916                             presentations.getInlinePresentation(),
917                             presentations.getInlineTooltipPresentation(), filter,
918                             presentations.getDialogPresentation());
919                 }
920             }
921             return this;
922         }
923 
924         /**
925          * Sets the value of a field with an <a href="#Filtering">explicit filter</a>, and using an
926          * {@link InlinePresentation} to visualize it as an inline suggestion.
927          *
928          * <p>Only called by augmented autofill.
929          *
930          * @param id id returned by {@link
931          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
932          * @param value the value to be autofilled. Pass {@code null} if you do not have the value
933          *        but the target view is a logical part of the dataset. For example, if
934          *        the dataset needs authentication and you have no access to the value.
935          * @param filter regex used to determine if the dataset should be shown in the autofill UI;
936          *        when {@code null}, it disables filtering on that dataset (this is the recommended
937          *        approach when {@code value} is not {@code null} and field contains sensitive data
938          *        such as passwords).
939          * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
940          *        as inline suggestions. If the dataset supports inline suggestions, this
941          *        should not be null.
942          *
943          * @throws IllegalStateException if {@link #build()} was already called.
944          *
945          * @return this builder.
946          * @deprecated Use {@link #setField(AutofillId, Field)} instead.
947          * @hide
948          */
949         @Deprecated
950         @SystemApi
setFieldInlinePresentation(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull InlinePresentation inlinePresentation)951         public @NonNull Builder setFieldInlinePresentation(@NonNull AutofillId id,
952                 @Nullable AutofillValue value, @Nullable Pattern filter,
953                 @NonNull InlinePresentation inlinePresentation) {
954             throwIfDestroyed();
955             Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
956             setLifeTheUniverseAndEverything(id, value, null, inlinePresentation, null,
957                     new DatasetFieldFilter(filter), null);
958             return this;
959         }
960 
setLifeTheUniverseAndEverything(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation tooltip, @Nullable DatasetFieldFilter filter, @Nullable RemoteViews dialogPresentation)961         private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
962                 @Nullable AutofillValue value, @Nullable RemoteViews presentation,
963                 @Nullable InlinePresentation inlinePresentation,
964                 @Nullable InlinePresentation tooltip,
965                 @Nullable DatasetFieldFilter filter,
966                 @Nullable RemoteViews dialogPresentation) {
967             Objects.requireNonNull(id, "id cannot be null");
968             if (mFieldIds != null) {
969                 final int existingIdx = mFieldIds.indexOf(id);
970                 if (existingIdx >= 0) {
971                     mFieldValues.set(existingIdx, value);
972                     mFieldPresentations.set(existingIdx, presentation);
973                     mFieldDialogPresentations.set(existingIdx, dialogPresentation);
974                     mFieldInlinePresentations.set(existingIdx, inlinePresentation);
975                     mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
976                     mFieldFilters.set(existingIdx, filter);
977                     return;
978                 }
979             } else {
980                 mFieldIds = new ArrayList<>();
981                 mFieldValues = new ArrayList<>();
982                 mFieldPresentations = new ArrayList<>();
983                 mFieldDialogPresentations = new ArrayList<>();
984                 mFieldInlinePresentations = new ArrayList<>();
985                 mFieldInlineTooltipPresentations = new ArrayList<>();
986                 mFieldFilters = new ArrayList<>();
987             }
988             mFieldIds.add(id);
989             mFieldValues.add(value);
990             mFieldPresentations.add(presentation);
991             mFieldDialogPresentations.add(dialogPresentation);
992             mFieldInlinePresentations.add(inlinePresentation);
993             mFieldInlineTooltipPresentations.add(tooltip);
994             mFieldFilters.add(filter);
995         }
996 
997         /**
998          * Creates a new {@link Dataset} instance.
999          *
1000          * <p>You should not interact with this builder once this method is called.
1001          *
1002          * @throws IllegalStateException if no field was set (through
1003          * {@link #setField(AutofillId, Field)}), or if {@link #build()} was already called.
1004          *
1005          * @return The built dataset.
1006          */
build()1007         public @NonNull Dataset build() {
1008             throwIfDestroyed();
1009             mDestroyed = true;
1010             if (mFieldIds == null) {
1011                 throw new IllegalStateException("at least one value must be set");
1012             }
1013             if (mFieldContent != null) {
1014                 if (mFieldIds.size() > 1) {
1015                     throw new IllegalStateException(
1016                             "when filling content, only one field can be filled");
1017                 }
1018                 if (mFieldValues.get(0) != null) {
1019                     throw new IllegalStateException("cannot fill both content and values");
1020                 }
1021             }
1022             return new Dataset(this);
1023         }
1024 
throwIfDestroyed()1025         private void throwIfDestroyed() {
1026             if (mDestroyed) {
1027                 throw new IllegalStateException("Already called #build()");
1028             }
1029         }
1030     }
1031 
1032     /////////////////////////////////////
1033     //  Parcelable "contract" methods. //
1034     /////////////////////////////////////
1035 
1036     @Override
describeContents()1037     public int describeContents() {
1038         return 0;
1039     }
1040 
1041     @Override
writeToParcel(Parcel parcel, int flags)1042     public void writeToParcel(Parcel parcel, int flags) {
1043         parcel.writeParcelable(mPresentation, flags);
1044         parcel.writeParcelable(mDialogPresentation, flags);
1045         parcel.writeParcelable(mInlinePresentation, flags);
1046         parcel.writeParcelable(mInlineTooltipPresentation, flags);
1047         parcel.writeTypedList(mFieldIds, flags);
1048         parcel.writeTypedList(mFieldValues, flags);
1049         parcel.writeTypedList(mFieldPresentations, flags);
1050         parcel.writeTypedList(mFieldDialogPresentations, flags);
1051         parcel.writeTypedList(mFieldInlinePresentations, flags);
1052         parcel.writeTypedList(mFieldInlineTooltipPresentations, flags);
1053         parcel.writeTypedList(mFieldFilters, flags);
1054         parcel.writeParcelable(mFieldContent, flags);
1055         parcel.writeParcelable(mAuthentication, flags);
1056         parcel.writeString(mId);
1057     }
1058 
1059     public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
1060         @Override
1061         public Dataset createFromParcel(Parcel parcel) {
1062             final RemoteViews presentation = parcel.readParcelable(null,
1063                     android.widget.RemoteViews.class);
1064             final RemoteViews dialogPresentation = parcel.readParcelable(null,
1065                     android.widget.RemoteViews.class);
1066             final InlinePresentation inlinePresentation = parcel.readParcelable(null,
1067                     android.service.autofill.InlinePresentation.class);
1068             final InlinePresentation inlineTooltipPresentation =
1069                     parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
1070             final ArrayList<AutofillId> ids =
1071                     parcel.createTypedArrayList(AutofillId.CREATOR);
1072             final ArrayList<AutofillValue> values =
1073                     parcel.createTypedArrayList(AutofillValue.CREATOR);
1074             final ArrayList<RemoteViews> presentations =
1075                     parcel.createTypedArrayList(RemoteViews.CREATOR);
1076             final ArrayList<RemoteViews> dialogPresentations =
1077                     parcel.createTypedArrayList(RemoteViews.CREATOR);
1078             final ArrayList<InlinePresentation> inlinePresentations =
1079                     parcel.createTypedArrayList(InlinePresentation.CREATOR);
1080             final ArrayList<InlinePresentation> inlineTooltipPresentations =
1081                     parcel.createTypedArrayList(InlinePresentation.CREATOR);
1082             final ArrayList<DatasetFieldFilter> filters =
1083                     parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
1084             final ClipData fieldContent = parcel.readParcelable(null,
1085                     android.content.ClipData.class);
1086             final IntentSender authentication = parcel.readParcelable(null,
1087                     android.content.IntentSender.class);
1088             final String datasetId = parcel.readString();
1089 
1090             // Always go through the builder to ensure the data ingested by
1091             // the system obeys the contract of the builder to avoid attacks
1092             // using specially crafted parcels.
1093             final Builder builder;
1094             if (presentation != null || inlinePresentation != null || dialogPresentation != null) {
1095                 final Presentations.Builder presentationsBuilder = new Presentations.Builder();
1096                 if (presentation != null) {
1097                     presentationsBuilder.setMenuPresentation(presentation);
1098                 }
1099                 if (inlinePresentation != null) {
1100                     presentationsBuilder.setInlinePresentation(inlinePresentation);
1101                 }
1102                 if (inlineTooltipPresentation != null) {
1103                     presentationsBuilder.setInlineTooltipPresentation(inlineTooltipPresentation);
1104                 }
1105                 if (dialogPresentation != null) {
1106                     presentationsBuilder.setDialogPresentation(dialogPresentation);
1107                 }
1108                 builder = new Builder(presentationsBuilder.build());
1109             } else {
1110                 builder = new Builder();
1111             }
1112 
1113             if (fieldContent != null) {
1114                 builder.setContent(ids.get(0), fieldContent);
1115             }
1116             final int inlinePresentationsSize = inlinePresentations.size();
1117             for (int i = 0; i < ids.size(); i++) {
1118                 final AutofillId id = ids.get(i);
1119                 final AutofillValue value = values.get(i);
1120                 final RemoteViews fieldPresentation = presentations.get(i);
1121                 final RemoteViews fieldDialogPresentation = dialogPresentations.get(i);
1122                 final InlinePresentation fieldInlinePresentation =
1123                         i < inlinePresentationsSize ? inlinePresentations.get(i) : null;
1124                 final InlinePresentation fieldInlineTooltipPresentation =
1125                         i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null;
1126                 final DatasetFieldFilter filter = filters.get(i);
1127                 builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation,
1128                         fieldInlinePresentation, fieldInlineTooltipPresentation, filter,
1129                         fieldDialogPresentation);
1130             }
1131             builder.setAuthentication(authentication);
1132             builder.setId(datasetId);
1133             return builder.build();
1134         }
1135 
1136         @Override
1137         public Dataset[] newArray(int size) {
1138             return new Dataset[size];
1139         }
1140     };
1141 
1142     /**
1143      * Helper class used to indicate when the service explicitly set a {@link Pattern} filter for a
1144      * dataset field&dash; we cannot use a {@link Pattern} directly because then we wouldn't be
1145      * able to differentiate whether the service explicitly passed a {@code null} filter to disable
1146      * filter, or when it called the methods that does not take a filter {@link Pattern}.
1147      *
1148      * @hide
1149      */
1150     public static final class DatasetFieldFilter implements Parcelable {
1151 
1152         @Nullable
1153         public final Pattern pattern;
1154 
DatasetFieldFilter(@ullable Pattern pattern)1155         DatasetFieldFilter(@Nullable Pattern pattern) {
1156             this.pattern = pattern;
1157         }
1158 
1159         @Override
toString()1160         public String toString() {
1161             if (!sDebug) return super.toString();
1162 
1163             // Cannot log pattern because it could contain PII
1164             return pattern == null ? "null" : pattern.pattern().length() + "_chars";
1165         }
1166 
1167         @Override
describeContents()1168         public int describeContents() {
1169             return 0;
1170         }
1171 
1172         @Override
writeToParcel(Parcel parcel, int flags)1173         public void writeToParcel(Parcel parcel, int flags) {
1174             parcel.writeSerializable(pattern);
1175         }
1176 
1177         @SuppressWarnings("hiding")
1178         public static final @android.annotation.NonNull Creator<DatasetFieldFilter> CREATOR =
1179                 new Creator<DatasetFieldFilter>() {
1180 
1181             @Override
1182             public DatasetFieldFilter createFromParcel(Parcel parcel) {
1183                 return new DatasetFieldFilter((Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class));
1184             }
1185 
1186             @Override
1187             public DatasetFieldFilter[] newArray(int size) {
1188                 return new DatasetFieldFilter[size];
1189             }
1190         };
1191     }
1192 }
1193