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