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.service.autofill.AutofillServiceHelper.assertValid;
20 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
21 import static android.view.autofill.Helper.sDebug;
22 
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.SuppressLint;
27 import android.annotation.TestApi;
28 import android.app.Activity;
29 import android.content.IntentSender;
30 import android.content.pm.ParceledListSlice;
31 import android.os.Bundle;
32 import android.os.Parcel;
33 import android.os.Parcelable;
34 import android.view.autofill.AutofillId;
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.Arrays;
43 import java.util.List;
44 
45 /**
46  * Response for an {@link
47  * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}.
48  *
49  * <p>See the main {@link AutofillService} documentation for more details and examples.
50  */
51 public final class FillResponse implements Parcelable {
52 
53     /**
54      * Flag used to generate {@link FillEventHistory.Event events} of type
55      * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}&mdash;if this flag is not passed to
56      * {@link Builder#setFlags(int)}, these events are not generated.
57      */
58     public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
59 
60     /**
61      * Flag used to change the behavior of {@link FillResponse.Builder#disableAutofill(long)}&mdash;
62      * when this flag is passed to {@link Builder#setFlags(int)}, autofill is disabled only for the
63      * activiy that generated the {@link FillRequest}, not the whole app.
64      */
65     public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
66 
67     /** @hide */
68     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
69             FLAG_TRACK_CONTEXT_COMMITED,
70             FLAG_DISABLE_ACTIVITY_ONLY
71     })
72     @Retention(RetentionPolicy.SOURCE)
73     @interface FillResponseFlags {}
74 
75     private final @Nullable ParceledListSlice<Dataset> mDatasets;
76     private final @Nullable SaveInfo mSaveInfo;
77     private final @Nullable Bundle mClientState;
78     private final @Nullable RemoteViews mPresentation;
79     private final @Nullable InlinePresentation mInlinePresentation;
80     private final @Nullable InlinePresentation mInlineTooltipPresentation;
81     private final @Nullable RemoteViews mHeader;
82     private final @Nullable RemoteViews mFooter;
83     private final @Nullable IntentSender mAuthentication;
84     private final @Nullable AutofillId[] mAuthenticationIds;
85     private final @Nullable AutofillId[] mIgnoredIds;
86     private final long mDisableDuration;
87     private final @Nullable AutofillId[] mFieldClassificationIds;
88     private final int mFlags;
89     private int mRequestId;
90     private final @Nullable UserData mUserData;
91     private final @Nullable int[] mCancelIds;
92     private final boolean mSupportsInlineSuggestions;
93 
FillResponse(@onNull Builder builder)94     private FillResponse(@NonNull Builder builder) {
95         mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
96         mSaveInfo = builder.mSaveInfo;
97         mClientState = builder.mClientState;
98         mPresentation = builder.mPresentation;
99         mInlinePresentation = builder.mInlinePresentation;
100         mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
101         mHeader = builder.mHeader;
102         mFooter = builder.mFooter;
103         mAuthentication = builder.mAuthentication;
104         mAuthenticationIds = builder.mAuthenticationIds;
105         mIgnoredIds = builder.mIgnoredIds;
106         mDisableDuration = builder.mDisableDuration;
107         mFieldClassificationIds = builder.mFieldClassificationIds;
108         mFlags = builder.mFlags;
109         mRequestId = INVALID_REQUEST_ID;
110         mUserData = builder.mUserData;
111         mCancelIds = builder.mCancelIds;
112         mSupportsInlineSuggestions = builder.mSupportsInlineSuggestions;
113     }
114 
115     /** @hide */
getClientState()116     public @Nullable Bundle getClientState() {
117         return mClientState;
118     }
119 
120     /** @hide */
getDatasets()121     public @Nullable List<Dataset> getDatasets() {
122         return (mDatasets != null) ? mDatasets.getList() : null;
123     }
124 
125     /** @hide */
getSaveInfo()126     public @Nullable SaveInfo getSaveInfo() {
127         return mSaveInfo;
128     }
129 
130     /** @hide */
getPresentation()131     public @Nullable RemoteViews getPresentation() {
132         return mPresentation;
133     }
134 
135     /** @hide */
getInlinePresentation()136     public @Nullable InlinePresentation getInlinePresentation() {
137         return mInlinePresentation;
138     }
139 
140     /** @hide */
getInlineTooltipPresentation()141     public @Nullable InlinePresentation getInlineTooltipPresentation() {
142         return mInlineTooltipPresentation;
143     }
144 
145     /** @hide */
getHeader()146     public @Nullable RemoteViews getHeader() {
147         return mHeader;
148     }
149 
150     /** @hide */
getFooter()151     public @Nullable RemoteViews getFooter() {
152         return mFooter;
153     }
154 
155     /** @hide */
getAuthentication()156     public @Nullable IntentSender getAuthentication() {
157         return mAuthentication;
158     }
159 
160     /** @hide */
getAuthenticationIds()161     public @Nullable AutofillId[] getAuthenticationIds() {
162         return mAuthenticationIds;
163     }
164 
165     /** @hide */
getIgnoredIds()166     public @Nullable AutofillId[] getIgnoredIds() {
167         return mIgnoredIds;
168     }
169 
170     /** @hide */
getDisableDuration()171     public long getDisableDuration() {
172         return mDisableDuration;
173     }
174 
175     /** @hide */
getFieldClassificationIds()176     public @Nullable AutofillId[] getFieldClassificationIds() {
177         return mFieldClassificationIds;
178     }
179 
180     /** @hide */
getUserData()181     public @Nullable UserData getUserData() {
182         return mUserData;
183     }
184 
185     /** @hide */
186     @TestApi
getFlags()187     public int getFlags() {
188         return mFlags;
189     }
190 
191     /**
192      * Associates a {@link FillResponse} to a request.
193      *
194      * <p>Set inside of the {@link FillCallback} code, not the {@link AutofillService}.
195      *
196      * @param requestId The id of the request to associate the response to.
197      *
198      * @hide
199      */
setRequestId(int requestId)200     public void setRequestId(int requestId) {
201         mRequestId = requestId;
202     }
203 
204     /** @hide */
getRequestId()205     public int getRequestId() {
206         return mRequestId;
207     }
208 
209     /** @hide */
210     @Nullable
getCancelIds()211     public int[] getCancelIds() {
212         return mCancelIds;
213     }
214 
215     /** @hide */
supportsInlineSuggestions()216     public boolean supportsInlineSuggestions() {
217         return mSupportsInlineSuggestions;
218     }
219 
220     /**
221      * Builder for {@link FillResponse} objects. You must to provide at least
222      * one dataset or set an authentication intent with a presentation view.
223      */
224     public static final class Builder {
225         private ArrayList<Dataset> mDatasets;
226         private SaveInfo mSaveInfo;
227         private Bundle mClientState;
228         private RemoteViews mPresentation;
229         private InlinePresentation mInlinePresentation;
230         private InlinePresentation mInlineTooltipPresentation;
231         private RemoteViews mHeader;
232         private RemoteViews mFooter;
233         private IntentSender mAuthentication;
234         private AutofillId[] mAuthenticationIds;
235         private AutofillId[] mIgnoredIds;
236         private long mDisableDuration;
237         private AutofillId[] mFieldClassificationIds;
238         private int mFlags;
239         private boolean mDestroyed;
240         private UserData mUserData;
241         private int[] mCancelIds;
242         private boolean mSupportsInlineSuggestions;
243 
244         /**
245          * Triggers a custom UI before before autofilling the screen with any data set in this
246          * response.
247          *
248          * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
249          * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
250          * for examples.
251          *
252          * <p>This is typically useful when a user interaction is required to unlock their
253          * data vault if you encrypt the data set labels and data set data. It is recommended
254          * to encrypt only the sensitive data and not the data set labels which would allow
255          * auth on the data set level leading to a better user experience. Note that if you
256          * use sensitive data as a label, for example an email address, then it should also
257          * be encrypted. The provided {@link android.app.PendingIntent intent} must be an
258          * {@link Activity} which implements your authentication flow. Also if you provide an auth
259          * intent you also need to specify the presentation view to be shown in the fill UI
260          * for the user to trigger your authentication flow.
261          *
262          * <p>When a user triggers autofill, the system launches the provided intent
263          * whose extras will have the
264          * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen
265          * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE
266          * client state}. Once you complete your authentication flow you should set the
267          * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and set the
268          * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra
269          * with the fully populated {@link FillResponse response} (or {@code null} if the screen
270          * cannot be autofilled).
271          *
272          * <p>For example, if you provided an empty {@link FillResponse response} because the
273          * user's data was locked and marked that the response needs an authentication then
274          * in the response returned if authentication succeeds you need to provide all
275          * available data sets some of which may need to be further authenticated, for
276          * example a credit card whose CVV needs to be entered.
277          *
278          * <p>If you provide an authentication intent you must also provide a presentation
279          * which is used to visualize visualize the response for triggering the authentication
280          * flow.
281          *
282          * <p><b>Note:</b> Do not make the provided pending intent
283          * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
284          * platform needs to fill in the authentication arguments.
285          *
286          * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
287          * or background color: Autofill on different platforms may have different themes.
288          *
289          * @param authentication Intent to an activity with your authentication flow.
290          * @param presentation The presentation to visualize the response.
291          * @param ids id of Views that when focused will display the authentication UI.
292          *
293          * @return This builder.
294          *
295          * @throws IllegalArgumentException if any of the following occurs:
296          * <ul>
297          *   <li>{@code ids} is {@code null}</li>
298          *   <li>{@code ids} is empty</li>
299          *   <li>{@code ids} contains a {@code null} element</li>
300          *   <li>both {@code authentication} and {@code presentation} are {@code null}</li>
301          *   <li>both {@code authentication} and {@code presentation} are non-{@code null}</li>
302          * </ul>
303          *
304          * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a
305          * {@link #setFooter(RemoteViews) footer} are already set for this builder.
306          *
307          * @see android.app.PendingIntent#getIntentSender()
308          */
309         @NonNull
setAuthentication(@onNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation)310         public Builder setAuthentication(@NonNull AutofillId[] ids,
311                 @Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
312             throwIfDestroyed();
313             throwIfDisableAutofillCalled();
314             if (mHeader != null || mFooter != null) {
315                 throw new IllegalStateException("Already called #setHeader() or #setFooter()");
316             }
317 
318             if (authentication == null ^ presentation == null) {
319                 throw new IllegalArgumentException("authentication and presentation"
320                         + " must be both non-null or null");
321             }
322             mAuthentication = authentication;
323             mPresentation = presentation;
324             mAuthenticationIds = assertValid(ids);
325             return this;
326         }
327 
328         /**
329          * Triggers a custom UI before before autofilling the screen with any data set in this
330          * response.
331          *
332          * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
333          * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
334          * for examples.
335          *
336          * <p>This method is similar to
337          * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, but also accepts
338          * an {@link InlinePresentation} presentation which is required for authenticating through
339          * the inline autofill flow.
340          *
341          * <p><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does
342          * not work with {@link InlinePresentation}.</p>
343          *
344          * @param authentication Intent to an activity with your authentication flow.
345          * @param presentation The presentation to visualize the response.
346          * @param inlinePresentation The inlinePresentation to visualize the response inline.
347          * @param ids id of Views that when focused will display the authentication UI.
348          *
349          * @return This builder.
350          *
351          * @throws IllegalArgumentException if any of the following occurs:
352          * <ul>
353          *   <li>{@code ids} is {@code null}</li>
354          *   <li>{@code ids} is empty</li>
355          *   <li>{@code ids} contains a {@code null} element</li>
356          *   <li>both {@code authentication} and {@code presentation} are {@code null}</li>
357          *   <li>both {@code authentication} and {@code presentation} are non-{@code null}</li>
358          *   <li>both {@code authentication} and {@code inlinePresentation} are {@code null}</li>
359          *   <li>both {@code authentication} and {@code inlinePresentation} are
360          *   non-{@code null}</li>
361          * </ul>
362          *
363          * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a
364          * {@link #setFooter(RemoteViews) footer} are already set for this builder.
365          *
366          * @see android.app.PendingIntent#getIntentSender()
367          */
368         @NonNull
setAuthentication(@onNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation)369         public Builder setAuthentication(@NonNull AutofillId[] ids,
370                 @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
371                 @Nullable InlinePresentation inlinePresentation) {
372             return setAuthentication(ids, authentication, presentation, inlinePresentation, null);
373         }
374 
375         /**
376          * Triggers a custom UI before before autofilling the screen with any data set in this
377          * response.
378          *
379          * <p>This method like
380          * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews, InlinePresentation)}
381          * but allows setting an {@link InlinePresentation} for the inline suggestion tooltip.
382          */
383         @NonNull
setAuthentication(@uppressLintR) @onNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation inlineTooltipPresentation)384         public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
385                 @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
386                 @Nullable InlinePresentation inlinePresentation,
387                 @Nullable InlinePresentation inlineTooltipPresentation) {
388             throwIfDestroyed();
389             throwIfDisableAutofillCalled();
390             if (mHeader != null || mFooter != null) {
391                 throw new IllegalStateException("Already called #setHeader() or #setFooter()");
392             }
393 
394             if (authentication == null ^ (presentation == null && inlinePresentation == null)) {
395                 throw new IllegalArgumentException("authentication and presentation "
396                         + "(dropdown or inline), must be both non-null or null");
397             }
398             mAuthentication = authentication;
399             mPresentation = presentation;
400             mInlinePresentation = inlinePresentation;
401             mInlineTooltipPresentation = inlineTooltipPresentation;
402             mAuthenticationIds = assertValid(ids);
403             return this;
404         }
405 
406         /**
407          * Specifies views that should not trigger new
408          * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
409          * FillCallback)} requests.
410          *
411          * <p>This is typically used when the service cannot autofill the view; for example, a
412          * text field representing the result of a Captcha challenge.
413          */
414         @NonNull
setIgnoredIds(AutofillId...ids)415         public Builder setIgnoredIds(AutofillId...ids) {
416             throwIfDestroyed();
417             mIgnoredIds = ids;
418             return this;
419         }
420 
421         /**
422          * Adds a new {@link Dataset} to this response.
423          *
424          * <p><b>Note: </b> on Android {@link android.os.Build.VERSION_CODES#O}, the total number of
425          * datasets is limited by the Binder transaction size, so it's recommended to keep it
426          * small (in the range of 10-20 at most) and use pagination by adding a fake
427          * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated dataset} at the end
428          * with a presentation string like "Next 10" that would return a new {@link FillResponse}
429          * with the next 10 datasets, and so on. This limitation was lifted on
430          * Android {@link android.os.Build.VERSION_CODES#O_MR1}, although the Binder transaction
431          * size can still be reached if each dataset itself is too big.
432          *
433          * @return This builder.
434          */
435         @NonNull
addDataset(@ullable Dataset dataset)436         public Builder addDataset(@Nullable Dataset dataset) {
437             throwIfDestroyed();
438             throwIfDisableAutofillCalled();
439             if (dataset == null) {
440                 return this;
441             }
442             if (mDatasets == null) {
443                 mDatasets = new ArrayList<>();
444             }
445             if (!mDatasets.add(dataset)) {
446                 return this;
447             }
448             return this;
449         }
450 
451         /**
452          * Sets the {@link SaveInfo} associated with this response.
453          *
454          * @return This builder.
455          */
setSaveInfo(@onNull SaveInfo saveInfo)456         public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) {
457             throwIfDestroyed();
458             throwIfDisableAutofillCalled();
459             mSaveInfo = saveInfo;
460             return this;
461         }
462 
463         /**
464          * Sets a bundle with state that is passed to subsequent APIs that manipulate this response.
465          *
466          * <p>You can use this bundle to store intermediate state that is passed to subsequent calls
467          * to {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
468          * FillCallback)} and {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}, and
469          * you can also retrieve it by calling {@link FillEventHistory.Event#getClientState()}.
470          *
471          * <p>If this method is called on multiple {@link FillResponse} objects for the same
472          * screen, just the latest bundle is passed back to the service.
473          *
474          * @param clientState The custom client state.
475          * @return This builder.
476          */
477         @NonNull
setClientState(@ullable Bundle clientState)478         public Builder setClientState(@Nullable Bundle clientState) {
479             throwIfDestroyed();
480             throwIfDisableAutofillCalled();
481             mClientState = clientState;
482             return this;
483         }
484 
485         /**
486          * Sets which fields are used for
487          * <a href="AutofillService.html#FieldClassification">field classification</a>
488          *
489          * <p><b>Note:</b> This method automatically adds the
490          * {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} to the {@link #setFlags(int) flags}.
491 
492          * @throws IllegalArgumentException is length of {@code ids} args is more than
493          * {@link UserData#getMaxFieldClassificationIdsSize()}.
494          * @throws IllegalStateException if {@link #build()} or {@link #disableAutofill(long)} was
495          * already called.
496          * @throws NullPointerException if {@code ids} or any element on it is {@code null}.
497          */
498         @NonNull
setFieldClassificationIds(@onNull AutofillId... ids)499         public Builder setFieldClassificationIds(@NonNull AutofillId... ids) {
500             throwIfDestroyed();
501             throwIfDisableAutofillCalled();
502             Preconditions.checkArrayElementsNotNull(ids, "ids");
503             Preconditions.checkArgumentInRange(ids.length, 1,
504                     UserData.getMaxFieldClassificationIdsSize(), "ids length");
505             mFieldClassificationIds = ids;
506             mFlags |= FLAG_TRACK_CONTEXT_COMMITED;
507             return this;
508         }
509 
510         /**
511          * Sets flags changing the response behavior.
512          *
513          * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and
514          * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}.
515          *
516          * @return This builder.
517          */
518         @NonNull
setFlags(@illResponseFlags int flags)519         public Builder setFlags(@FillResponseFlags int flags) {
520             throwIfDestroyed();
521             mFlags = Preconditions.checkFlagsArgument(flags,
522                     FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
523             return this;
524         }
525 
526         /**
527          * Disables autofill for the app or activity.
528          *
529          * <p>This method is useful to optimize performance in cases where the service knows it
530          * can not autofill an app&mdash;for example, when the service has a list of "denylisted"
531          * apps such as office suites.
532          *
533          * <p>By default, it disables autofill for all activities in the app, unless the response is
534          * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}.
535          *
536          * <p>Autofill for the app or activity is automatically re-enabled after any of the
537          * following conditions:
538          *
539          * <ol>
540          *   <li>{@code duration} milliseconds have passed.
541          *   <li>The autofill service for the user has changed.
542          *   <li>The device has rebooted.
543          * </ol>
544          *
545          * <p><b>Note:</b> Activities that are running when autofill is re-enabled remain
546          * disabled for autofill until they finish and restart.
547          *
548          * @param duration duration to disable autofill, in milliseconds.
549          *
550          * @return this builder
551          *
552          * @throws IllegalArgumentException if {@code duration} is not a positive number.
553          * @throws IllegalStateException if either {@link #addDataset(Dataset)},
554          *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
555          *       {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or
556          *       {@link #setFieldClassificationIds(AutofillId...)} was already called.
557          */
558         @NonNull
disableAutofill(long duration)559         public Builder disableAutofill(long duration) {
560             throwIfDestroyed();
561             if (duration <= 0) {
562                 throw new IllegalArgumentException("duration must be greater than 0");
563             }
564             if (mAuthentication != null || mDatasets != null || mSaveInfo != null
565                     || mFieldClassificationIds != null || mClientState != null) {
566                 throw new IllegalStateException("disableAutofill() must be the only method called");
567             }
568 
569             mDisableDuration = duration;
570             return this;
571         }
572 
573         /**
574          * Sets a header to be shown as the first element in the list of datasets.
575          *
576          * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset},
577          * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this
578          * method should only be used on {@link FillResponse FillResponses} that do not require
579          * authentication (as the header could have been set directly in the main presentation in
580          * these cases).
581          *
582          * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
583          * or background color: Autofill on different platforms may have different themes.
584          *
585          * @param header a presentation to represent the header. This presentation is not clickable
586          * &mdash;calling
587          * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would
588          * have no effect.
589          *
590          * @return this builder
591          *
592          * @throws IllegalStateException if an
593          * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was
594          * already set for this builder.
595          */
596         // TODO(b/69796626): make it sticky / update javadoc
597         @NonNull
setHeader(@onNull RemoteViews header)598         public Builder setHeader(@NonNull RemoteViews header) {
599             throwIfDestroyed();
600             throwIfAuthenticationCalled();
601             mHeader = Preconditions.checkNotNull(header);
602             return this;
603         }
604 
605         /**
606          * Sets a footer to be shown as the last element in the list of datasets.
607          *
608          * <p>When this method is called, you must also {@link #addDataset(Dataset) add a dataset},
609          * otherwise {@link #build()} throws an {@link IllegalStateException}. Similarly, this
610          * method should only be used on {@link FillResponse FillResponses} that do not require
611          * authentication (as the footer could have been set directly in the main presentation in
612          * these cases).
613          *
614          * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
615          * or background color: Autofill on different platforms may have different themes.
616          *
617          * @param footer a presentation to represent the footer. This presentation is not clickable
618          * &mdash;calling
619          * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent)} on it would
620          * have no effect.
621          *
622          * @return this builder
623          *
624          * @throws IllegalStateException if the FillResponse
625          * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
626          * requires authentication}.
627          */
628         // TODO(b/69796626): make it sticky / update javadoc
629         @NonNull
setFooter(@onNull RemoteViews footer)630         public Builder setFooter(@NonNull RemoteViews footer) {
631             throwIfDestroyed();
632             throwIfAuthenticationCalled();
633             mFooter = Preconditions.checkNotNull(footer);
634             return this;
635         }
636 
637         /**
638          * Sets a specific {@link UserData} for field classification for this request only.
639          *
640          * <p>Any fields in this UserData will override corresponding fields in the generic
641          * UserData object
642          *
643          * @return this builder
644          * @throws IllegalStateException if the FillResponse
645          * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
646          * requires authentication}.
647          */
648         @NonNull
setUserData(@onNull UserData userData)649         public Builder setUserData(@NonNull UserData userData) {
650             throwIfDestroyed();
651             throwIfAuthenticationCalled();
652             mUserData = Preconditions.checkNotNull(userData);
653             return this;
654         }
655 
656         /**
657          * Sets target resource IDs of the child view in {@link RemoteViews Presentation Template}
658          * which will cancel the session when clicked.
659          * Those targets will be respectively applied to a child of the header, footer and
660          * each {@link Dataset}.
661          *
662          * @param ids array of the resource id. Empty list or non-existing id has no effect.
663          *
664          * @return this builder
665          *
666          * @throws IllegalStateException if {@link #build()} was already called.
667          */
668         @NonNull
setPresentationCancelIds(@ullable int[] ids)669         public Builder setPresentationCancelIds(@Nullable int[] ids) {
670             throwIfDestroyed();
671             mCancelIds = ids;
672             return this;
673         }
674 
675         /**
676          * Builds a new {@link FillResponse} instance.
677          *
678          * @throws IllegalStateException if any of the following conditions occur:
679          * <ol>
680          *   <li>{@link #build()} was already called.
681          *   <li>No call was made to {@link #addDataset(Dataset)},
682          *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
683          *       {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)},
684          *       {@link #setClientState(Bundle)},
685          *       or {@link #setFieldClassificationIds(AutofillId...)}.
686          *   <li>{@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} is called
687          *       without any previous calls to {@link #addDataset(Dataset)}.
688          * </ol>
689          *
690          * @return A built response.
691          */
692         @NonNull
build()693         public FillResponse build() {
694             throwIfDestroyed();
695             if (mAuthentication == null && mDatasets == null && mSaveInfo == null
696                     && mDisableDuration == 0 && mFieldClassificationIds == null
697                     && mClientState == null) {
698                 throw new IllegalStateException("need to provide: at least one DataSet, or a "
699                         + "SaveInfo, or an authentication with a presentation, "
700                         + "or a FieldsDetection, or a client state, or disable autofill");
701             }
702             if (mDatasets == null && (mHeader != null || mFooter != null)) {
703                 throw new IllegalStateException(
704                         "must add at least 1 dataset when using header or footer");
705             }
706 
707             if (mDatasets != null) {
708                 for (final Dataset dataset : mDatasets) {
709                     if (dataset.getFieldInlinePresentation(0) != null) {
710                         mSupportsInlineSuggestions = true;
711                         break;
712                     }
713                 }
714             } else if (mInlinePresentation != null) {
715                 mSupportsInlineSuggestions = true;
716             }
717 
718             mDestroyed = true;
719             return new FillResponse(this);
720         }
721 
throwIfDestroyed()722         private void throwIfDestroyed() {
723             if (mDestroyed) {
724                 throw new IllegalStateException("Already called #build()");
725             }
726         }
727 
throwIfDisableAutofillCalled()728         private void throwIfDisableAutofillCalled() {
729             if (mDisableDuration > 0) {
730                 throw new IllegalStateException("Already called #disableAutofill()");
731             }
732         }
733 
throwIfAuthenticationCalled()734         private void throwIfAuthenticationCalled() {
735             if (mAuthentication != null) {
736                 throw new IllegalStateException("Already called #setAuthentication()");
737             }
738         }
739     }
740 
741     /////////////////////////////////////
742     // Object "contract" methods. //
743     /////////////////////////////////////
744     @Override
toString()745     public String toString() {
746         if (!sDebug) return super.toString();
747 
748         // TODO: create a dump() method instead
749         final StringBuilder builder = new StringBuilder(
750                 "FillResponse : [mRequestId=" + mRequestId);
751         if (mDatasets != null) {
752             builder.append(", datasets=").append(mDatasets.getList());
753         }
754         if (mSaveInfo != null) {
755             builder.append(", saveInfo=").append(mSaveInfo);
756         }
757         if (mClientState != null) {
758             builder.append(", hasClientState");
759         }
760         if (mPresentation != null) {
761             builder.append(", hasPresentation");
762         }
763         if (mInlinePresentation != null) {
764             builder.append(", hasInlinePresentation");
765         }
766         if (mInlineTooltipPresentation != null) {
767             builder.append(", hasInlineTooltipPresentation");
768         }
769         if (mHeader != null) {
770             builder.append(", hasHeader");
771         }
772         if (mFooter != null) {
773             builder.append(", hasFooter");
774         }
775         if (mAuthentication != null) {
776             builder.append(", hasAuthentication");
777         }
778         if (mAuthenticationIds != null) {
779             builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds));
780         }
781         builder.append(", disableDuration=").append(mDisableDuration);
782         if (mFlags != 0) {
783             builder.append(", flags=").append(mFlags);
784         }
785         if (mFieldClassificationIds != null) {
786             builder.append(Arrays.toString(mFieldClassificationIds));
787         }
788         if (mUserData != null) {
789             builder.append(", userData=").append(mUserData);
790         }
791         if (mCancelIds != null) {
792             builder.append(", mCancelIds=").append(mCancelIds.length);
793         }
794         builder.append(", mSupportInlinePresentations=").append(mSupportsInlineSuggestions);
795         return builder.append("]").toString();
796     }
797 
798     /////////////////////////////////////
799     // Parcelable "contract" methods. //
800     /////////////////////////////////////
801 
802     @Override
describeContents()803     public int describeContents() {
804         return 0;
805     }
806 
807     @Override
writeToParcel(Parcel parcel, int flags)808     public void writeToParcel(Parcel parcel, int flags) {
809         parcel.writeParcelable(mDatasets, flags);
810         parcel.writeParcelable(mSaveInfo, flags);
811         parcel.writeParcelable(mClientState, flags);
812         parcel.writeParcelableArray(mAuthenticationIds, flags);
813         parcel.writeParcelable(mAuthentication, flags);
814         parcel.writeParcelable(mPresentation, flags);
815         parcel.writeParcelable(mInlinePresentation, flags);
816         parcel.writeParcelable(mInlineTooltipPresentation, flags);
817         parcel.writeParcelable(mHeader, flags);
818         parcel.writeParcelable(mFooter, flags);
819         parcel.writeParcelable(mUserData, flags);
820         parcel.writeParcelableArray(mIgnoredIds, flags);
821         parcel.writeLong(mDisableDuration);
822         parcel.writeParcelableArray(mFieldClassificationIds, flags);
823         parcel.writeInt(mFlags);
824         parcel.writeIntArray(mCancelIds);
825         parcel.writeInt(mRequestId);
826     }
827 
828     public static final @android.annotation.NonNull Parcelable.Creator<FillResponse> CREATOR =
829             new Parcelable.Creator<FillResponse>() {
830         @Override
831         public FillResponse createFromParcel(Parcel parcel) {
832             // Always go through the builder to ensure the data ingested by
833             // the system obeys the contract of the builder to avoid attacks
834             // using specially crafted parcels.
835             final Builder builder = new Builder();
836             final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null);
837             final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null;
838             final int datasetCount = (datasets != null) ? datasets.size() : 0;
839             for (int i = 0; i < datasetCount; i++) {
840                 builder.addDataset(datasets.get(i));
841             }
842             builder.setSaveInfo(parcel.readParcelable(null));
843             builder.setClientState(parcel.readParcelable(null));
844 
845             // Sets authentication state.
846             final AutofillId[] authenticationIds = parcel.readParcelableArray(null,
847                     AutofillId.class);
848             final IntentSender authentication = parcel.readParcelable(null);
849             final RemoteViews presentation = parcel.readParcelable(null);
850             final InlinePresentation inlinePresentation = parcel.readParcelable(null);
851             final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null);
852             if (authenticationIds != null) {
853                 builder.setAuthentication(authenticationIds, authentication, presentation,
854                         inlinePresentation, inlineTooltipPresentation);
855             }
856             final RemoteViews header = parcel.readParcelable(null);
857             if (header != null) {
858                 builder.setHeader(header);
859             }
860             final RemoteViews footer = parcel.readParcelable(null);
861             if (footer != null) {
862                 builder.setFooter(footer);
863             }
864             final UserData userData = parcel.readParcelable(null);
865             if (userData != null) {
866                 builder.setUserData(userData);
867             }
868 
869             builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
870             final long disableDuration = parcel.readLong();
871             if (disableDuration > 0) {
872                 builder.disableAutofill(disableDuration);
873             }
874             final AutofillId[] fieldClassifactionIds =
875                     parcel.readParcelableArray(null, AutofillId.class);
876             if (fieldClassifactionIds != null) {
877                 builder.setFieldClassificationIds(fieldClassifactionIds);
878             }
879             builder.setFlags(parcel.readInt());
880             final int[] cancelIds = parcel.createIntArray();
881             builder.setPresentationCancelIds(cancelIds);
882 
883             final FillResponse response = builder.build();
884             response.setRequestId(parcel.readInt());
885 
886             return response;
887         }
888 
889         @Override
890         public FillResponse[] newArray(int size) {
891             return new FillResponse[size];
892         }
893     };
894 }
895