1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.autofillservice.cts.testcore;
17 
18 import static android.autofillservice.cts.testcore.Helper.createInlinePresentation;
19 import static android.autofillservice.cts.testcore.Helper.createPresentation;
20 import static android.autofillservice.cts.testcore.Helper.getAutofillIds;
21 
22 import static com.google.common.truth.Truth.assertWithMessage;
23 
24 import android.app.assist.AssistStructure;
25 import android.app.assist.AssistStructure.ViewNode;
26 import android.content.IntentSender;
27 import android.os.Bundle;
28 import android.service.autofill.Dataset;
29 import android.service.autofill.Field;
30 import android.service.autofill.FillCallback;
31 import android.service.autofill.FillContext;
32 import android.service.autofill.FillResponse;
33 import android.service.autofill.InlinePresentation;
34 import android.service.autofill.Presentations;
35 import android.service.autofill.SaveInfo;
36 import android.service.autofill.UserData;
37 import android.util.Log;
38 import android.util.Pair;
39 import android.view.autofill.AutofillId;
40 import android.view.autofill.AutofillManager;
41 import android.view.autofill.AutofillValue;
42 import android.widget.RemoteViews;
43 
44 import androidx.annotation.NonNull;
45 import androidx.annotation.Nullable;
46 
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.HashMap;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.function.Function;
53 import java.util.regex.Pattern;
54 
55 /**
56  * Helper class used to produce a {@link FillResponse} based on expected fields that should be
57  * present in the {@link AssistStructure}.
58  *
59  * <p>Typical usage:
60  *
61  * <pre class="prettyprint">
62  * InstrumentedAutoFillService.getReplier().addResponse(new CannedFillResponse.Builder()
63  *               .addDataset(new CannedDataset.Builder("dataset_name")
64  *                   .setField("resource_id1", AutofillValue.forText("value1"))
65  *                   .setField("resource_id2", AutofillValue.forText("value2"))
66  *                   .build())
67  *               .build());
68  * </pre class="prettyprint">
69  */
70 public final class CannedFillResponse {
71 
72     private static final String TAG = CannedFillResponse.class.getSimpleName();
73 
74     private final ResponseType mResponseType;
75     private final List<CannedDataset> mDatasets;
76     private final String mFailureMessage;
77     private final int mSaveType;
78     private final String[] mRequiredSavableIds;
79     private final String[] mOptionalSavableIds;
80     private final AutofillId[] mRequiredSavableAutofillIds;
81     private final CharSequence mSaveDescription;
82     private final Bundle mExtras;
83     private final RemoteViews mPresentation;
84     private final InlinePresentation mInlinePresentation;
85     private final RemoteViews mHeader;
86     private final RemoteViews mFooter;
87     private final IntentSender mAuthentication;
88     private final String[] mAuthenticationIds;
89     private final String[] mIgnoredIds;
90     private final int mNegativeActionStyle;
91     private final IntentSender mNegativeActionListener;
92     private final int mPositiveActionStyle;
93     private final int mSaveInfoFlags;
94     private final int mFillResponseFlags;
95     private final AutofillId mSaveTriggerId;
96     private final long mDisableDuration;
97     private final String[] mFieldClassificationIds;
98     private final boolean mFieldClassificationIdsOverflow;
99     private final SaveInfoDecorator mSaveInfoDecorator;
100     private final UserData mUserData;
101     private final DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
102     private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
103     private final int[] mCancelIds;
104     private final String[] mDialogTriggerIds;
105     private final RemoteViews mDialogHeaderPresentation;
106     private final int mIconResourceId;
107     private final int mServiceDisplayNameResourceId;
108     private final boolean mShowFillDialogIcon;
109     private final boolean mShowSaveDialogIcon;
110 
111 
CannedFillResponse(Builder builder)112     private CannedFillResponse(Builder builder) {
113         mResponseType = builder.mResponseType;
114         mDatasets = builder.mDatasets;
115         mFailureMessage = builder.mFailureMessage;
116         mRequiredSavableIds = builder.mRequiredSavableIds;
117         mRequiredSavableAutofillIds = builder.mRequiredSavableAutofillIds;
118         mOptionalSavableIds = builder.mOptionalSavableIds;
119         mSaveDescription = builder.mSaveDescription;
120         mSaveType = builder.mSaveType;
121         mExtras = builder.mExtras;
122         mPresentation = builder.mPresentation;
123         mInlinePresentation = builder.mInlinePresentation;
124         mHeader = builder.mHeader;
125         mFooter = builder.mFooter;
126         mAuthentication = builder.mAuthentication;
127         mAuthenticationIds = builder.mAuthenticationIds;
128         mIgnoredIds = builder.mIgnoredIds;
129         mNegativeActionStyle = builder.mNegativeActionStyle;
130         mNegativeActionListener = builder.mNegativeActionListener;
131         mPositiveActionStyle = builder.mPositiveActionStyle;
132         mSaveInfoFlags = builder.mSaveInfoFlags;
133         mFillResponseFlags = builder.mFillResponseFlags;
134         mSaveTriggerId = builder.mSaveTriggerId;
135         mDisableDuration = builder.mDisableDuration;
136         mFieldClassificationIds = builder.mFieldClassificationIds;
137         mFieldClassificationIdsOverflow = builder.mFieldClassificationIdsOverflow;
138         mSaveInfoDecorator = builder.mSaveInfoDecorator;
139         mUserData = builder.mUserData;
140         mVisitor = builder.mVisitor;
141         mSaveInfoVisitor = builder.mSaveInfoVisitor;
142         mCancelIds = builder.mCancelIds;
143         mDialogTriggerIds = builder.mDialogTriggerIds;
144         mDialogHeaderPresentation = builder.mDialogHeaderPresentation;
145         mIconResourceId = builder.mIconResourceId;
146         mServiceDisplayNameResourceId = builder.mServiceDisplayNameResourceId;
147         mShowFillDialogIcon = builder.mShowFillDialogIcon;
148         mShowSaveDialogIcon = builder.mShowSaveDialogIcon;
149     }
150 
151     /**
152      * Constant used to pass a {@code null} response to the
153      * {@link FillCallback#onSuccess(FillResponse)} method.
154      */
155     public static final CannedFillResponse NO_RESPONSE =
156             new Builder(ResponseType.NULL).build();
157 
158     /**
159      * Constant used to fail the test when an expected request was made.
160      */
161     public static final CannedFillResponse NO_MOAR_RESPONSES =
162             new Builder(ResponseType.NO_MORE).build();
163 
164     /**
165      * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
166      */
167     public static final CannedFillResponse DO_NOT_REPLY_RESPONSE =
168             new Builder(ResponseType.TIMEOUT).build();
169 
170     /**
171      * Constant used to call {@link FillCallback#onFailure(CharSequence)} method.
172      */
173     public static final CannedFillResponse FAIL =
174             new Builder(ResponseType.FAILURE).build();
175 
getFailureMessage()176     public String getFailureMessage() {
177         return mFailureMessage;
178     }
179 
getResponseType()180     public ResponseType getResponseType() {
181         return mResponseType;
182     }
183 
184     /**
185      * Creates a new response, replacing the dataset field ids by the real ids from the assist
186      * structure.
187      */
asFillResponse(@ullable List<FillContext> contexts, @NonNull Function<String, ViewNode> nodeResolver)188     public FillResponse asFillResponse(@Nullable List<FillContext> contexts,
189             @NonNull Function<String, ViewNode> nodeResolver) {
190         return asFillResponseWithAutofillId(contexts, (id)-> {
191             ViewNode node = nodeResolver.apply(id);
192             if (node == null) {
193                 throw new AssertionError("No node with resource id " + id);
194             }
195             return node.getAutofillId();
196         });
197     }
198 
asFillResponseWithAutofillId(@ullable List<FillContext> contexts, @NonNull Function<String, AutofillId> autofillIdResolver)199     public FillResponse asFillResponseWithAutofillId(@Nullable List<FillContext> contexts,
200             @NonNull Function<String, AutofillId> autofillIdResolver) {
201         return asFillResponseWithAutofillId(contexts, autofillIdResolver,
202             (cannedDataset) -> {
203                 return cannedDataset.asDatasetWithAutofillIdResolver(autofillIdResolver);
204             });
205     }
206 
207     private Function<String, AutofillId> getAutofillIdResolver(
208              @NonNull Function<String, ViewNode> nodeResolver) {
209         return (id) -> {
210             ViewNode node = nodeResolver.apply(id);
211             if (node == null) {
212                 throw new AssertionError("No node with resource id " + id);
213             }
214             return node.getAutofillId();
215         };
216     }
217 
218     public FillResponse asPccFillResponse(@Nullable List<FillContext> contexts,
219             @NonNull Function<String, ViewNode> nodeResolver) {
220         final Function<String, AutofillId> autofillPccResolver =
221                 (id)-> {
222                     ViewNode node = nodeResolver.apply(id);
223                     if (node == null) {
224                         return null;
225                     }
226                     return node.getAutofillId();
227                 };
228         return asFillResponseWithAutofillId(
229                 contexts,
230                 autofillPccResolver,
231                 (cannedDataset) -> cannedDataset.asDatasetForPcc(autofillPccResolver));
232     }
233 
234     /**
235      * Creates a new response, replacing the dataset field ids by the real ids from the assist
236      * structure.
237      */
238     public FillResponse asFillResponseWithAutofillId(@Nullable List<FillContext> contexts,
239             @NonNull Function<String, AutofillId> autofillIdResolver,
240             Function<CannedDataset, Dataset> cannedDatasetToDataset) {
241         final FillResponse.Builder builder = new FillResponse.Builder()
242                 .setFlags(mFillResponseFlags);
243         if (mDatasets != null) {
244             for (CannedDataset cannedDataset : mDatasets) {
245                 final Dataset dataset = cannedDatasetToDataset.apply(cannedDataset);
246                 assertWithMessage("Cannot create dataset").that(dataset).isNotNull();
247                 builder.addDataset(dataset);
248             }
249         }
250         final SaveInfo.Builder saveInfoBuilder;
251         if (mRequiredSavableIds != null || mOptionalSavableIds != null
252                 || mRequiredSavableAutofillIds != null || mSaveInfoDecorator != null) {
253             if (mRequiredSavableAutofillIds != null) {
254                 saveInfoBuilder = new SaveInfo.Builder(mSaveType, mRequiredSavableAutofillIds);
255             } else {
256                 saveInfoBuilder = mRequiredSavableIds == null || mRequiredSavableIds.length == 0
257                         ? new SaveInfo.Builder(mSaveType)
258                             : new SaveInfo.Builder(mSaveType,
259                                     getAutofillIds(autofillIdResolver, mRequiredSavableIds));
260             }
261 
262             saveInfoBuilder.setFlags(mSaveInfoFlags);
263 
264             if (mOptionalSavableIds != null) {
265                 saveInfoBuilder.setOptionalIds(
266                         getAutofillIds(autofillIdResolver, mOptionalSavableIds));
267             }
268             if (mSaveDescription != null) {
269                 saveInfoBuilder.setDescription(mSaveDescription);
270             }
271             if (mNegativeActionListener != null) {
272                 saveInfoBuilder.setNegativeAction(mNegativeActionStyle, mNegativeActionListener);
273             }
274 
275             saveInfoBuilder.setPositiveAction(mPositiveActionStyle);
276 
277             if (mSaveTriggerId != null) {
278                 saveInfoBuilder.setTriggerId(mSaveTriggerId);
279             }
280         } else if (mSaveInfoFlags != 0) {
281             saveInfoBuilder = new SaveInfo.Builder(mSaveType).setFlags(mSaveInfoFlags);
282         } else {
283             saveInfoBuilder = null;
284         }
285         if (saveInfoBuilder != null) {
286             // TODO: merge decorator and visitor
287             if (mSaveInfoDecorator != null) {
288                 mSaveInfoDecorator.decorate(saveInfoBuilder, autofillIdResolver);
289             }
290             if (mSaveInfoVisitor != null) {
291                 Log.d(TAG, "Visiting saveInfo " + saveInfoBuilder);
292                 mSaveInfoVisitor.visit(contexts, saveInfoBuilder);
293             }
294             final SaveInfo saveInfo = saveInfoBuilder.build();
295             Log.d(TAG, "saveInfo:" + saveInfo);
296             builder.setSaveInfo(saveInfo);
297         }
298         if (mIgnoredIds != null) {
299             builder.setIgnoredIds(getAutofillIds(autofillIdResolver, mIgnoredIds));
300         }
301         if (mAuthenticationIds != null) {
302             builder.setAuthentication(getAutofillIds(autofillIdResolver, mAuthenticationIds),
303                     mAuthentication, mPresentation, mInlinePresentation);
304         }
305         if (mDisableDuration > 0) {
306             builder.disableAutofill(mDisableDuration);
307         }
308         if (mFieldClassificationIdsOverflow) {
309             final int length = UserData.getMaxFieldClassificationIdsSize() + 1;
310             final AutofillId[] fieldIds = new AutofillId[length];
311             for (int i = 0; i < length; i++) {
312                 fieldIds[i] = new AutofillId(i);
313             }
314             builder.setFieldClassificationIds(fieldIds);
315         } else if (mFieldClassificationIds != null) {
316             builder.setFieldClassificationIds(
317                     getAutofillIds(autofillIdResolver, mFieldClassificationIds));
318         }
319         if (mExtras != null) {
320             builder.setClientState(mExtras);
321         }
322         if (mHeader != null) {
323             builder.setHeader(mHeader);
324         }
325         if (mFooter != null) {
326             builder.setFooter(mFooter);
327         }
328         if (mUserData != null) {
329             builder.setUserData(mUserData);
330         }
331         if (mVisitor != null) {
332             Log.d(TAG, "Visiting " + builder);
333             mVisitor.visit(contexts, builder);
334         }
335         builder.setPresentationCancelIds(mCancelIds);
336         if (mDialogTriggerIds != null) {
337             builder.setFillDialogTriggerIds(
338                     getAutofillIds(autofillIdResolver, mDialogTriggerIds));
339         }
340         if (mDialogHeaderPresentation != null) {
341             builder.setDialogHeader(mDialogHeaderPresentation);
342         }
343 
344         builder.setIconResourceId(mIconResourceId);
345         builder.setServiceDisplayNameResourceId(mServiceDisplayNameResourceId);
346         builder.setShowFillDialogIcon(mShowFillDialogIcon);
347         builder.setShowSaveDialogIcon(mShowSaveDialogIcon);
348 
349         final FillResponse response = builder.build();
350         Log.v(TAG, "Response: " + response);
351         return response;
352     }
353 
354     @Override
355     public String toString() {
356         return "CannedFillResponse: [type=" + mResponseType
357                 + ",datasets=" + mDatasets
358                 + ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds)
359                 + ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds)
360                 + ", requiredSavableAutofillIds=" + Arrays.toString(mRequiredSavableAutofillIds)
361                 + ", saveInfoFlags=" + mSaveInfoFlags
362                 + ", fillResponseFlags=" + mFillResponseFlags
363                 + ", failureMessage=" + mFailureMessage
364                 + ", saveDescription=" + mSaveDescription
365                 + ", hasPresentation=" + (mPresentation != null)
366                 + ", hasInlinePresentation=" + (mInlinePresentation != null)
367                 + ", hasHeader=" + (mHeader != null)
368                 + ", hasFooter=" + (mFooter != null)
369                 + ", hasAuthentication=" + (mAuthentication != null)
370                 + ", authenticationIds=" + Arrays.toString(mAuthenticationIds)
371                 + ", ignoredIds=" + Arrays.toString(mIgnoredIds)
372                 + ", saveTriggerId=" + mSaveTriggerId
373                 + ", disableDuration=" + mDisableDuration
374                 + ", fieldClassificationIds=" + Arrays.toString(mFieldClassificationIds)
375                 + ", fieldClassificationIdsOverflow=" + mFieldClassificationIdsOverflow
376                 + ", saveInfoDecorator=" + mSaveInfoDecorator
377                 + ", userData=" + mUserData
378                 + ", visitor=" + mVisitor
379                 + ", saveInfoVisitor=" + mSaveInfoVisitor
380                 + "]";
381     }
382 
383     public enum ResponseType {
384         NORMAL,
385         NULL,
386         NO_MORE,
387         TIMEOUT,
388         FAILURE,
389         DELAY
390     }
391 
392     public static final class Builder {
393         private final List<CannedDataset> mDatasets = new ArrayList<>();
394         private final ResponseType mResponseType;
395         private String mFailureMessage;
396         private String[] mRequiredSavableIds;
397         private String[] mOptionalSavableIds;
398         private AutofillId[] mRequiredSavableAutofillIds;
399         private CharSequence mSaveDescription;
400         public int mSaveType = -1;
401         private Bundle mExtras;
402         private RemoteViews mPresentation;
403         private InlinePresentation mInlinePresentation;
404         private RemoteViews mFooter;
405         private RemoteViews mHeader;
406         private IntentSender mAuthentication;
407         private String[] mAuthenticationIds;
408         private String[] mIgnoredIds;
409         private int mNegativeActionStyle;
410         private IntentSender mNegativeActionListener;
411         private int mPositiveActionStyle;
412         private int mSaveInfoFlags;
413         private int mFillResponseFlags;
414         private AutofillId mSaveTriggerId;
415         private long mDisableDuration;
416         private String[] mFieldClassificationIds;
417         private boolean mFieldClassificationIdsOverflow;
418         private SaveInfoDecorator mSaveInfoDecorator;
419         private UserData mUserData;
420         private DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
421         private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
422         private int[] mCancelIds;
423         private String[] mDialogTriggerIds;
424         private RemoteViews mDialogHeaderPresentation;
425         private int mIconResourceId;
426         private int mServiceDisplayNameResourceId;
427         private boolean mShowFillDialogIcon = true;
428         private boolean mShowSaveDialogIcon = true;
429 
430 
431         public Builder(ResponseType type) {
432             mResponseType = type;
433         }
434 
435         public Builder() {
436             this(ResponseType.NORMAL);
437         }
438 
439         public Builder addDataset(CannedDataset dataset) {
440             assertWithMessage("already set failure").that(mFailureMessage).isNull();
441             mDatasets.add(dataset);
442             return this;
443         }
444 
445         public Builder setIconResourceId(int id) {
446             mIconResourceId = id;
447             return this;
448         }
449 
450         public Builder setServiceDisplayNameResourceId(int id) {
451             mServiceDisplayNameResourceId = id;
452             return this;
453         }
454 
455         public Builder setShowFillDialogIcon(boolean show) {
456             mShowFillDialogIcon = show;
457             return this;
458         }
459 
460         public Builder setShowSaveDialogIcon(boolean show) {
461             mShowSaveDialogIcon = show;
462             return this;
463         }
464 
465         /**
466          * Sets the required savable ids based on their {@code resourceId}.
467          */
468         public Builder setRequiredSavableIds(int type, String... ids) {
469             mSaveType = type;
470             mRequiredSavableIds = ids;
471             return this;
472         }
473 
474         /**
475          * Sets the valid Save types, for when PCC Detection is enabled
476          */
477         public Builder setSaveTypes(int type) {
478             mSaveType = type;
479             return this;
480         }
481 
482         public Builder setSaveInfoFlags(int flags) {
483             mSaveInfoFlags = flags;
484             return this;
485         }
486 
487         public Builder setFillResponseFlags(int flags) {
488             mFillResponseFlags = flags;
489             return this;
490         }
491 
492         /**
493          * Sets the optional savable ids based on they {@code resourceId}.
494          */
495         public Builder setOptionalSavableIds(String... ids) {
496             mOptionalSavableIds = ids;
497             return this;
498         }
499 
500         /**
501          * Sets the description passed to the {@link SaveInfo}.
502          */
503         public Builder setSaveDescription(CharSequence description) {
504             mSaveDescription = description;
505             return this;
506         }
507 
508         /**
509          * Sets the extra passed to {@link
510          * android.service.autofill.FillResponse.Builder#setClientState(Bundle)}.
511          */
512         public Builder setExtras(Bundle data) {
513             mExtras = data;
514             return this;
515         }
516 
517         /**
518          * Sets the view to present the response in the UI.
519          */
520         public Builder setPresentation(RemoteViews presentation) {
521             mPresentation = presentation;
522             return this;
523         }
524 
525         /**
526          * Sets the view to present the response in the UI.
527          */
528         public Builder setInlinePresentation(InlinePresentation inlinePresentation) {
529             mInlinePresentation = inlinePresentation;
530             return this;
531         }
532 
533         /**
534          * Sets views to present the response in the UI by the type.
535          */
536         public Builder setPresentation(String message, boolean inlineMode) {
537             mPresentation = createPresentation(message);
538             if (inlineMode) {
539                 mInlinePresentation = createInlinePresentation(message);
540             }
541             return this;
542         }
543 
544         /**
545          * Sets the authentication intent.
546          */
547         public Builder setAuthentication(IntentSender authentication, String... ids) {
548             mAuthenticationIds = ids;
549             mAuthentication = authentication;
550             return this;
551         }
552 
553         /**
554          * Sets the ignored fields based on resource ids.
555          */
556         public Builder setIgnoreFields(String...ids) {
557             mIgnoredIds = ids;
558             return this;
559         }
560 
561         /**
562          * Sets the negative action spec.
563          */
564         public Builder setNegativeAction(int style, IntentSender listener) {
565             mNegativeActionStyle = style;
566             mNegativeActionListener = listener;
567             return this;
568         }
569 
570         /**
571          * Sets the positive action spec.
572          */
573         public Builder setPositiveAction(int style) {
574             mPositiveActionStyle = style;
575             return this;
576         }
577 
578         public CannedFillResponse build() {
579             return new CannedFillResponse(this);
580         }
581 
582         /**
583          * Sets the response to call {@link FillCallback#onFailure(CharSequence)}.
584          */
585         public Builder returnFailure(String message) {
586             assertWithMessage("already added datasets").that(mDatasets).isEmpty();
587             mFailureMessage = message;
588             return this;
589         }
590 
591         /**
592          * Sets the view that explicitly triggers save.
593          */
594         public Builder setSaveTriggerId(AutofillId id) {
595             assertWithMessage("already set").that(mSaveTriggerId).isNull();
596             mSaveTriggerId = id;
597             return this;
598         }
599 
600         public Builder disableAutofill(long duration) {
601             assertWithMessage("already set").that(mDisableDuration).isEqualTo(0L);
602             mDisableDuration = duration;
603             return this;
604         }
605 
606         /**
607          * Sets the ids used for field classification.
608          */
609         public Builder setFieldClassificationIds(String... ids) {
610             assertWithMessage("already set").that(mFieldClassificationIds).isNull();
611             mFieldClassificationIds = ids;
612             return this;
613         }
614 
615         /**
616          * Forces the service to throw an exception when setting the fields classification ids.
617          */
618         public Builder setFieldClassificationIdsOverflow() {
619             mFieldClassificationIdsOverflow = true;
620             return this;
621         }
622 
623         public Builder setHeader(RemoteViews header) {
624             assertWithMessage("already set").that(mHeader).isNull();
625             mHeader = header;
626             return this;
627         }
628 
629         public Builder setFooter(RemoteViews footer) {
630             assertWithMessage("already set").that(mFooter).isNull();
631             mFooter = footer;
632             return this;
633         }
634 
635         public Builder setSaveInfoDecorator(SaveInfoDecorator decorator) {
636             assertWithMessage("already set").that(mSaveInfoDecorator).isNull();
637             mSaveInfoDecorator = decorator;
638             return this;
639         }
640 
641         /**
642          * Sets the package-specific UserData.
643          *
644          * <p>Overrides the default UserData for field classification.
645          */
646         public Builder setUserData(UserData userData) {
647             assertWithMessage("already set").that(mUserData).isNull();
648             mUserData = userData;
649             return this;
650         }
651 
652         /**
653          * Sets a generic visitor for the "real" request and response.
654          *
655          * <p>Typically used in cases where the test need to infer data from the request to build
656          * the response.
657          */
658         public Builder setVisitor(
659                 @NonNull DoubleVisitor<List<FillContext>, FillResponse.Builder> visitor) {
660             mVisitor = visitor;
661             return this;
662         }
663 
664         /**
665          * Sets a generic visitor for the "real" request and save info.
666          *
667          * <p>Typically used in cases where the test need to infer data from the request to build
668          * the response.
669          */
670         public Builder setSaveInfoVisitor(
671                 @NonNull DoubleVisitor<List<FillContext>, SaveInfo.Builder> visitor) {
672             mSaveInfoVisitor = visitor;
673             return this;
674         }
675 
676         /**
677          * Sets targets that cancel current session
678          */
679         public Builder setPresentationCancelIds(int[] ids) {
680             mCancelIds = ids;
681             return this;
682         }
683 
684         /**
685          * Sets the id of views which trigger the fill dialog.
686          */
687         public Builder setDialogTriggerIds(String... ids) {
688             mDialogTriggerIds = ids;
689             return this;
690         }
691 
692         /**
693          * Sets the header of the fill dialog.
694          */
695         public Builder setDialogHeader(RemoteViews header) {
696             mDialogHeaderPresentation = header;
697             return this;
698         }
699     }
700 
701     /**
702      * Helper class used to produce a {@link Dataset} based on expected fields that should be
703      * present in the {@link AssistStructure}.
704      *
705      * <p>Typical usage:
706      *
707      * <pre class="prettyprint">
708      * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
709      *               .addDataset(new CannedDataset.Builder("dataset_name")
710      *                   .setField("resource_id1", AutofillValue.forText("value1"))
711      *                   .setField("resource_id2", AutofillValue.forText("value2"))
712      *                   .build())
713      *               .build());
714      * </pre class="prettyprint">
715      */
716     public static class CannedDataset {
717         private final Map<String, AutofillValue> mFieldValues;
718         private final Map<String, RemoteViews> mFieldPresentations;
719         private final Map<String, RemoteViews> mFieldDialogPresentations;
720         private final Map<String, InlinePresentation> mFieldInlinePresentations;
721         private final Map<String, InlinePresentation> mFieldInlineTooltipPresentations;
722         private final Map<String, Pair<Boolean, Pattern>> mFieldFilters;
723         private final RemoteViews mPresentation;
724         private final RemoteViews mDialogPresentation;
725         private final InlinePresentation mInlinePresentation;
726         private final InlinePresentation mInlineTooltipPresentation;
727         private final IntentSender mAuthentication;
728         private final String mId;
729 
730         private CannedDataset(Builder builder) {
731             mFieldValues = builder.mFieldValues;
732             mFieldPresentations = builder.mFieldPresentations;
733             mFieldDialogPresentations = builder.mFieldDialogPresentations;
734             mFieldInlinePresentations = builder.mFieldInlinePresentations;
735             mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations;
736             mFieldFilters = builder.mFieldFilters;
737             mPresentation = builder.mPresentation;
738             mDialogPresentation = builder.mDialogPresentation;
739             mInlinePresentation = builder.mInlinePresentation;
740             mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
741             mAuthentication = builder.mAuthentication;
742             mId = builder.mId;
743         }
744 
745         /**
746          * Creates a new dataset, replacing the field ids by the real ids from the assist structure.
747          */
748         public Dataset asDatasetWithNodeResolver(Function<String, ViewNode> nodeResolver) {
749             return asDatasetWithAutofillIdResolver((id) -> {
750                 ViewNode node = nodeResolver.apply(id);
751                 if (node == null) {
752                     throw new AssertionError("No node with resource id " + id);
753                 }
754                 return node.getAutofillId();
755             });
756         }
757 
758         public Dataset asDatasetForPcc(Function<String, AutofillId> autofillIdResolver) {
759             final Presentations.Builder presentationsBuilder = new Presentations.Builder();
760             if (mPresentation != null) {
761                 presentationsBuilder.setMenuPresentation(mPresentation);
762             }
763             if (mDialogPresentation != null) {
764                 presentationsBuilder.setDialogPresentation(mDialogPresentation);
765             }
766             if (mInlinePresentation != null) {
767                 presentationsBuilder.setInlinePresentation(mInlinePresentation);
768             }
769             if (mInlineTooltipPresentation != null) {
770                 presentationsBuilder.setInlineTooltipPresentation(mInlineTooltipPresentation);
771             }
772 
773             Presentations presentations = null;
774             try {
775                 presentations = presentationsBuilder.build();
776             } catch (IllegalStateException e) {
777                 // No presentation in presentationsBuilder, do nothing.
778             }
779             final Dataset.Builder builder = presentations != null
780                     ? new Dataset.Builder(presentations)
781                     : new Dataset.Builder();
782             if (mFieldValues != null) {
783                 for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
784                     final Field.Builder fieldBuilder = new Field.Builder();
785                     final AutofillValue value = entry.getValue();
786                     final String id = entry.getKey();
787                     if (value != null) {
788                         fieldBuilder.setValue(value);
789                     }
790                     final Presentations.Builder fieldPresentationsBuilder =
791                             new Presentations.Builder();
792                     final RemoteViews presentation = mFieldPresentations.get(id);
793                     if (presentation != null) {
794                         fieldPresentationsBuilder.setMenuPresentation(presentation);
795                     }
796                     final RemoteViews dialogPresentation = mFieldDialogPresentations.get(id);
797                     if (dialogPresentation != null) {
798                         fieldPresentationsBuilder.setDialogPresentation(dialogPresentation);
799                     }
800                     final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id);
801                     if (inlinePresentation != null) {
802                         fieldPresentationsBuilder.setInlinePresentation(inlinePresentation);
803                     }
804                     final InlinePresentation tooltipPresentation =
805                             mFieldInlineTooltipPresentations.get(id);
806                     if (tooltipPresentation != null) {
807                         fieldPresentationsBuilder.setInlineTooltipPresentation(tooltipPresentation);
808                     }
809                     try {
810                         fieldBuilder.setPresentations(fieldPresentationsBuilder.build());
811                     } catch (IllegalStateException e) {
812                         // no presentation in fieldPresentationsBuilder, nothing
813                     }
814                     final Pair<Boolean, Pattern> filter = mFieldFilters.get(id);
815                     if (filter != null) {
816                         fieldBuilder.setFilter(filter.second);
817                     }
818 
819                     final AutofillId autofillId = autofillIdResolver.apply(id);
820                     if (autofillId == null) {
821                         // Treat the id as autofill hints
822                         builder.setField(id, fieldBuilder.build());
823                     } else {
824                         builder.setField(autofillId, fieldBuilder.build());
825                     }
826                 }
827             }
828             builder.setId(mId).setAuthentication(mAuthentication);
829             return builder.build();
830         }
831 
832         /**
833          * Creates a new dataset, replacing the field ids by the real ids from the assist structure.
834          */
835         public Dataset asDatasetWithAutofillIdResolver(
836                 Function<String, AutofillId> autofillIdResolver) {
837             final Presentations.Builder presentationsBuilder = new Presentations.Builder();
838             if (mPresentation != null) {
839                 presentationsBuilder.setMenuPresentation(mPresentation);
840             }
841             if (mDialogPresentation != null) {
842                 presentationsBuilder.setDialogPresentation(mDialogPresentation);
843             }
844             if (mInlinePresentation != null) {
845                 presentationsBuilder.setInlinePresentation(mInlinePresentation);
846             }
847             if (mInlineTooltipPresentation != null) {
848                 presentationsBuilder.setInlineTooltipPresentation(mInlineTooltipPresentation);
849             }
850 
851             Presentations presentations = null;
852             try {
853                 presentations = presentationsBuilder.build();
854             } catch (IllegalStateException e) {
855                 // No presentation in presentationsBuilder, do nothing.
856             }
857             final Dataset.Builder builder = presentations != null
858                     ? new Dataset.Builder(presentations)
859                     : new Dataset.Builder();
860 
861             if (mFieldValues != null) {
862                 for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
863                     final String id = entry.getKey();
864 
865                     final AutofillId autofillId = autofillIdResolver.apply(id);
866                     if (autofillId == null) {
867                         throw new AssertionError("No node with resource id " + id);
868                     }
869                     final Field.Builder fieldBuilder = new Field.Builder();
870                     final AutofillValue value = entry.getValue();
871                     if (value != null) {
872                         fieldBuilder.setValue(value);
873                     }
874 
875                     final Presentations.Builder fieldPresentationsBuilder =
876                             new Presentations.Builder();
877                     final RemoteViews presentation = mFieldPresentations.get(id);
878                     if (presentation != null) {
879                         fieldPresentationsBuilder.setMenuPresentation(presentation);
880                     }
881                     final RemoteViews dialogPresentation = mFieldDialogPresentations.get(id);
882                     if (dialogPresentation != null) {
883                         fieldPresentationsBuilder.setDialogPresentation(dialogPresentation);
884                     }
885                     final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id);
886                     if (inlinePresentation != null) {
887                         fieldPresentationsBuilder.setInlinePresentation(inlinePresentation);
888                     }
889                     final InlinePresentation tooltipPresentation =
890                             mFieldInlineTooltipPresentations.get(id);
891                     if (tooltipPresentation != null) {
892                         fieldPresentationsBuilder.setInlineTooltipPresentation(tooltipPresentation);
893                     }
894                     try {
895                         fieldBuilder.setPresentations(fieldPresentationsBuilder.build());
896                     } catch (IllegalStateException e) {
897                         // no presentation in fieldPresentationsBuilder, nothing
898                     }
899                     final Pair<Boolean, Pattern> filter = mFieldFilters.get(id);
900                     if (filter != null) {
901                         fieldBuilder.setFilter(filter.second);
902                     }
903                     builder.setField(autofillId, fieldBuilder.build());
904                 }
905             }
906             builder.setId(mId).setAuthentication(mAuthentication);
907             return builder.build();
908         }
909 
910         @Override
911         public String toString() {
912             return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null)
913                     + ", hasDialogPresentation=" + (mDialogPresentation != null)
914                     + ", hasInlinePresentation=" + (mInlinePresentation != null)
915                     + ", fieldPresentations=" + (mFieldPresentations)
916                     + ", fieldDialogPresentations=" + (mFieldDialogPresentations)
917                     + ", fieldInlinePresentations=" + (mFieldInlinePresentations)
918                     + ", fieldTooltipInlinePresentations=" + (mFieldInlineTooltipPresentations)
919                     + ", hasAuthentication=" + (mAuthentication != null)
920                     + ", fieldValues=" + mFieldValues
921                     + ", fieldFilters=" + mFieldFilters + "]";
922         }
923 
924         public static class Builder {
925             private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
926             private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
927             private final Map<String, RemoteViews> mFieldDialogPresentations = new HashMap<>();
928             private final Map<String, InlinePresentation> mFieldInlinePresentations =
929                     new HashMap<>();
930             private final Map<String, InlinePresentation> mFieldInlineTooltipPresentations =
931                     new HashMap<>();
932             private final Map<String, Pair<Boolean, Pattern>> mFieldFilters = new HashMap<>();
933 
934             private RemoteViews mPresentation;
935             private RemoteViews mDialogPresentation;
936             private InlinePresentation mInlinePresentation;
937             private IntentSender mAuthentication;
938             private String mId;
939             private InlinePresentation mInlineTooltipPresentation;
940 
941             public Builder() {
942 
943             }
944 
945             public Builder(RemoteViews presentation) {
946                 mPresentation = presentation;
947             }
948 
949             /**
950              * Sets the canned value of a text field based on its {@code id}.
951              *
952              * <p>The meaning of the id is defined by the object using the canned dataset.
953              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
954              * {@link IdMode}.
955              */
956             public Builder setField(String id, String text) {
957                 return setField(id, AutofillValue.forText(text));
958             }
959 
960             /**
961              * Sets the canned value of a text field applicable for all hints.
962              * This is applicable to PCC related datasets entry only.
963              */
964             public Builder setField(String text) {
965                 return setField(AutofillManager.ANY_HINT, AutofillValue.forText(text));
966             }
967 
968             /**
969              * Sets the canned value of a text field based on its {@code id}.
970              *
971              * <p>The meaning of the id is defined by the object using the canned dataset.
972              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
973              * {@link IdMode}.
974              */
975             public Builder setField(String id, String text, Pattern filter) {
976                 return setField(id, AutofillValue.forText(text), true, filter);
977             }
978 
979             public Builder setUnfilterableField(String id, String text) {
980                 return setField(id, AutofillValue.forText(text), false, null);
981             }
982 
983             /**
984              * Sets the canned value of a list field based on its its {@code id}.
985              *
986              * <p>The meaning of the id is defined by the object using the canned dataset.
987              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
988              * {@link IdMode}.
989              */
990             public Builder setField(String id, int index) {
991                 return setField(id, AutofillValue.forList(index));
992             }
993 
994             /**
995              * Sets the canned value of a toggle field based on its {@code id}.
996              *
997              * <p>The meaning of the id is defined by the object using the canned dataset.
998              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
999              * {@link IdMode}.
1000              */
1001             public Builder setField(String id, boolean toggled) {
1002                 return setField(id, AutofillValue.forToggle(toggled));
1003             }
1004 
1005             /**
1006              * Sets the canned value of a date field based on its {@code id}.
1007              *
1008              * <p>The meaning of the id is defined by the object using the canned dataset.
1009              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
1010              * {@link IdMode}.
1011              */
1012             public Builder setField(String id, long date) {
1013                 return setField(id, AutofillValue.forDate(date));
1014             }
1015 
1016             /**
1017              * Sets the canned value of a date field based on its {@code id}.
1018              *
1019              * <p>The meaning of the id is defined by the object using the canned dataset.
1020              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
1021              * {@link IdMode}.
1022              */
1023             public Builder setField(String id, AutofillValue value) {
1024                 mFieldValues.put(id, value);
1025                 return this;
1026             }
1027 
1028             /**
1029              * Sets the canned value of a date field based on its {@code id}.
1030              *
1031              * <p>The meaning of the id is defined by the object using the canned dataset.
1032              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
1033              * {@link IdMode}.
1034              */
1035             public Builder setField(String id, AutofillValue value, boolean filterable,
1036                     Pattern filter) {
1037                 setField(id, value);
1038                 mFieldFilters.put(id, new Pair<>(filterable, filter));
1039                 return this;
1040             }
1041 
1042             /**
1043              * Sets the canned value of a field based on its {@code id}.
1044              *
1045              * <p>The meaning of the id is defined by the object using the canned dataset.
1046              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
1047              * {@link IdMode}.
1048              */
1049             public Builder setField(String id, String text, RemoteViews presentation) {
1050                 setField(id, text);
1051                 mFieldPresentations.put(id, presentation);
1052                 return this;
1053             }
1054 
1055             /**
1056              * Sets the canned value of a field based on its {@code id}.
1057              *
1058              * <p>The meaning of the id is defined by the object using the canned dataset.
1059              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
1060              * {@link IdMode}.
1061              */
1062             public Builder setField(String id, String text, RemoteViews presentation,
1063                     RemoteViews dialogPresentation) {
1064                 setField(id, text, presentation);
1065                 mFieldDialogPresentations.put(id, dialogPresentation);
1066                 return this;
1067             }
1068 
1069             /**
1070              * Sets the canned value of a field based on its {@code id}.
1071              *
1072              * <p>The meaning of the id is defined by the object using the canned dataset.
1073              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
1074              * {@link IdMode}.
1075              */
1076             public Builder setField(String id, String text, RemoteViews presentation,
1077                     Pattern filter) {
1078                 setField(id, text, presentation);
1079                 mFieldFilters.put(id, new Pair<>(true, filter));
1080                 return this;
1081             }
1082 
1083             /**
1084              * Sets the canned value of a field based on its {@code id}.
1085              *
1086              * <p>The meaning of the id is defined by the object using the canned dataset.
1087              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
1088              * {@link IdMode}.
1089              */
1090             public Builder setField(String id, String text, RemoteViews presentation,
1091                     InlinePresentation inlinePresentation) {
1092                 setField(id, text);
1093                 mFieldPresentations.put(id, presentation);
1094                 mFieldInlinePresentations.put(id, inlinePresentation);
1095                 return this;
1096             }
1097 
1098             /**
1099              * Sets the canned value of a field based on its {@code id}.
1100              *
1101              * <p>The meaning of the id is defined by the object using the canned dataset.
1102              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
1103              * {@link IdMode}.
1104              */
1105             public Builder setField(String id, String text, RemoteViews presentation,
1106                     InlinePresentation inlinePresentation,
1107                     InlinePresentation inlineTooltipPresentation) {
1108                 setField(id, text, presentation, inlinePresentation);
1109                 mFieldInlineTooltipPresentations.put(id, inlineTooltipPresentation);
1110                 return this;
1111             }
1112 
1113             /**
1114              * Sets the canned value of a field based on its {@code id}.
1115              *
1116              * <p>The meaning of the id is defined by the object using the canned dataset.
1117              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
1118              * {@link IdMode}.
1119              */
1120             public Builder setField(String id, String text, RemoteViews presentation,
1121                     InlinePresentation inlinePresentation, Pattern filter) {
1122                 setField(id, text, presentation, inlinePresentation);
1123                 mFieldFilters.put(id, new Pair<>(true, filter));
1124                 return this;
1125             }
1126 
1127             /**
1128              * Sets the canned value of a field based on its {@code id}.
1129              *
1130              * <p>The meaning of the id is defined by the object using the canned dataset.
1131              * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
1132              * {@link IdMode}.
1133              */
1134             public Builder setField(String id, String text, RemoteViews presentation,
1135                     InlinePresentation inlinePresentation,
1136                     InlinePresentation inlineTooltipPresentation,
1137                     Pattern filter) {
1138                 setField(id, text, presentation, inlinePresentation, inlineTooltipPresentation);
1139                 mFieldFilters.put(id, new Pair<>(true, filter));
1140 
1141                 return this;
1142             }
1143 
1144             /**
1145              * Sets the view to present the response in the UI.
1146              */
1147             public Builder setPresentation(RemoteViews presentation) {
1148                 mPresentation = presentation;
1149                 return this;
1150             }
1151 
1152             /**
1153              * Sets the view to present the response in the UI.
1154              */
1155             public Builder setInlinePresentation(InlinePresentation inlinePresentation) {
1156                 mInlinePresentation = inlinePresentation;
1157                 return this;
1158             }
1159 
1160             /**
1161              * Sets the inline tooltip to present the response in the UI.
1162              */
1163             public Builder setInlineTooltipPresentation(InlinePresentation tooltip) {
1164                 mInlineTooltipPresentation = tooltip;
1165                 return this;
1166             }
1167 
1168             public Builder setPresentation(String message, boolean inlineMode) {
1169                 mPresentation = createPresentation(message);
1170                 if (inlineMode) {
1171                     mInlinePresentation = createInlinePresentation(message);
1172                 }
1173                 return this;
1174             }
1175 
1176             /**
1177              * Sets the view to present the response in the UI.
1178              */
1179             public Builder setDialogPresentation(RemoteViews presentation) {
1180                 mDialogPresentation = presentation;
1181                 return this;
1182             }
1183 
1184             /**
1185              * Sets the authentication intent.
1186              */
1187             public Builder setAuthentication(IntentSender authentication) {
1188                 mAuthentication = authentication;
1189                 return this;
1190             }
1191 
1192             /**
1193              * Sets the name.
1194              */
1195             public Builder setId(String id) {
1196                 mId = id;
1197                 return this;
1198             }
1199 
1200             /**
1201              * Builds the canned dataset.
1202              */
1203             public CannedDataset build() {
1204                 return new CannedDataset(this);
1205             }
1206         }
1207     }
1208 
1209     public interface SaveInfoDecorator {
1210         void decorate(SaveInfo.Builder builder, Function<String, AutofillId> nodeResolver);
1211     }
1212 }
1213