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