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.saveui;
17 
18 import static android.autofillservice.cts.activities.LoginActivity.ID_USERNAME_CONTAINER;
19 import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_COMMIT;
20 import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
21 import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_LABEL;
22 import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD;
23 import static android.autofillservice.cts.activities.SimpleSaveActivity.TEXT_LABEL;
24 import static android.autofillservice.cts.testcore.AntiTrimmerTextWatcher.TRIMMER_PATTERN;
25 import static android.autofillservice.cts.testcore.Helper.ID_STATIC_TEXT;
26 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
27 import static android.autofillservice.cts.testcore.Helper.LARGE_STRING;
28 import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
29 import static android.autofillservice.cts.testcore.Helper.assertTextValue;
30 import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
31 import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
32 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
33 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
34 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
35 
36 import static com.google.common.truth.Truth.assertThat;
37 import static com.google.common.truth.Truth.assertWithMessage;
38 
39 import static org.junit.Assume.assumeTrue;
40 
41 import android.app.assist.AssistStructure;
42 import android.app.assist.AssistStructure.ViewNode;
43 import android.autofillservice.cts.R;
44 import android.autofillservice.cts.activities.LoginActivity;
45 import android.autofillservice.cts.activities.SecondActivity;
46 import android.autofillservice.cts.activities.SimpleSaveActivity;
47 import android.autofillservice.cts.activities.SimpleSaveActivity.FillExpectation;
48 import android.autofillservice.cts.activities.TrampolineWelcomeActivity;
49 import android.autofillservice.cts.activities.ViewActionActivity;
50 import android.autofillservice.cts.activities.WelcomeActivity;
51 import android.autofillservice.cts.commontests.CustomDescriptionWithLinkTestCase;
52 import android.autofillservice.cts.testcore.AntiTrimmerTextWatcher;
53 import android.autofillservice.cts.testcore.AutofillActivityTestRule;
54 import android.autofillservice.cts.testcore.CannedFillResponse;
55 import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
56 import android.autofillservice.cts.testcore.DismissType;
57 import android.autofillservice.cts.testcore.Helper;
58 import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
59 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
60 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
61 import android.autofillservice.cts.testcore.MyAutofillCallback;
62 import android.autofillservice.cts.testcore.MyAutofillId;
63 import android.autofillservice.cts.testcore.Timeouts;
64 import android.autofillservice.cts.testcore.UiBot;
65 import android.content.Intent;
66 import android.graphics.Bitmap;
67 import android.os.Bundle;
68 import android.platform.test.annotations.AppModeFull;
69 import android.platform.test.annotations.Presubmit;
70 import android.service.autofill.BatchUpdates;
71 import android.service.autofill.CustomDescription;
72 import android.service.autofill.FillContext;
73 import android.service.autofill.FillEventHistory;
74 import android.service.autofill.RegexValidator;
75 import android.service.autofill.SaveInfo;
76 import android.service.autofill.TextValueSanitizer;
77 import android.service.autofill.Validator;
78 import android.support.test.uiautomator.By;
79 import android.support.test.uiautomator.UiObject2;
80 import android.text.Spannable;
81 import android.text.SpannableString;
82 import android.text.style.URLSpan;
83 import android.view.View;
84 import android.view.autofill.AutofillId;
85 import android.widget.RemoteViews;
86 
87 import org.junit.Test;
88 import org.junit.rules.RuleChain;
89 import org.junit.rules.TestRule;
90 
91 import java.util.regex.Pattern;
92 
93 public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase<SimpleSaveActivity> {
94 
95     private static final AutofillActivityTestRule<SimpleSaveActivity> sActivityRule =
96             new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class, false);
97 
98     private static final AutofillActivityTestRule<WelcomeActivity> sWelcomeActivityRule =
99             new AutofillActivityTestRule<WelcomeActivity>(WelcomeActivity.class, false);
100 
SimpleSaveActivityTest()101     public SimpleSaveActivityTest() {
102         super(SimpleSaveActivity.class);
103     }
104 
105     @Override
getActivityRule()106     protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
107         return sActivityRule;
108     }
109 
110     @Override
getMainTestRule()111     protected TestRule getMainTestRule() {
112         return RuleChain.outerRule(sActivityRule).around(sWelcomeActivityRule);
113     }
114 
restartActivity()115     private void restartActivity() {
116         final Intent intent = new Intent(mContext.getApplicationContext(),
117                 SimpleSaveActivity.class);
118         intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
119         mActivity.startActivity(intent);
120     }
121 
122     @Presubmit
123     @Test
testAutoFillOneDatasetAndSave()124     public void testAutoFillOneDatasetAndSave() throws Exception {
125         startActivity();
126 
127         // Set service.
128         enableService();
129 
130         // Set expectations.
131         sReplier.addResponse(new CannedFillResponse.Builder()
132                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
133                 .addDataset(new CannedDataset.Builder()
134                         .setField(ID_INPUT, "id")
135                         .setField(ID_PASSWORD, "pass")
136                         .setPresentation(createPresentation("YO"))
137                         .build())
138                 .build());
139 
140         // Trigger autofill.
141         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
142         sReplier.getNextFillRequest();
143 
144         // Select dataset.
145         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
146         mUiBot.selectDataset("YO");
147         autofillExpecation.assertAutoFilled();
148 
149         mActivity.syncRunOnUiThread(() -> {
150             mActivity.mInput.setText("ID");
151             mActivity.mPassword.setText("PASS");
152             mActivity.mCommit.performClick();
153         });
154         final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
155 
156         // Save it...
157         mUiBot.saveForAutofill(saveUi, true);
158 
159         // ... and assert results
160         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
161         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
162         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
163     }
164 
165     @Test
166     @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
testAutoFillOneDatasetAndSave_largeAssistStructure()167     public void testAutoFillOneDatasetAndSave_largeAssistStructure() throws Exception {
168         startActivity();
169 
170         mActivity.syncRunOnUiThread(
171                 () -> mActivity.mInput.setAutofillHints(LARGE_STRING, LARGE_STRING, LARGE_STRING));
172 
173         // Set service.
174         enableService();
175 
176         // Set expectations.
177         sReplier.addResponse(new CannedFillResponse.Builder()
178                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
179                 .addDataset(new CannedDataset.Builder()
180                         .setField(ID_INPUT, "id")
181                         .setField(ID_PASSWORD, "pass")
182                         .setPresentation(createPresentation("YO"))
183                         .build())
184                 .build());
185 
186         // Trigger autofill.
187         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
188         final FillRequest fillRequest = sReplier.getNextFillRequest();
189         final ViewNode inputOnFill = findNodeByResourceId(fillRequest.structure, ID_INPUT);
190         final String[] hintsOnFill = inputOnFill.getAutofillHints();
191         // Cannot compare these large strings directly becauise it could cause ANR
192         assertThat(hintsOnFill).hasLength(3);
193         Helper.assertEqualsToLargeString(hintsOnFill[0]);
194         Helper.assertEqualsToLargeString(hintsOnFill[1]);
195         Helper.assertEqualsToLargeString(hintsOnFill[2]);
196 
197         // Select dataset.
198         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
199         mUiBot.selectDataset("YO");
200         autofillExpecation.assertAutoFilled();
201 
202         mActivity.syncRunOnUiThread(() -> {
203             mActivity.mInput.setText("ID");
204             mActivity.mPassword.setText("PASS");
205             mActivity.mCommit.performClick();
206         });
207         final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
208 
209         // Save it...
210         mUiBot.saveForAutofill(saveUi, true);
211 
212         // ... and assert results
213         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
214         final ViewNode inputOnSave = findNodeByResourceId(saveRequest.structure, ID_INPUT);
215         assertTextAndValue(inputOnSave, "ID");
216         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
217 
218         final String[] hintsOnSave = inputOnSave.getAutofillHints();
219         // Cannot compare these large strings directly becauise it could cause ANR
220         assertThat(hintsOnSave).hasLength(3);
221         Helper.assertEqualsToLargeString(hintsOnSave[0]);
222         Helper.assertEqualsToLargeString(hintsOnSave[1]);
223         Helper.assertEqualsToLargeString(hintsOnSave[2]);
224     }
225 
226     /**
227      * Simple test that only uses UiAutomator to interact with the activity, so it indirectly
228      * tests the integration of Autofill with Accessibility.
229      */
230     @Test
testAutoFillOneDatasetAndSave_usingUiAutomatorOnly()231     public void testAutoFillOneDatasetAndSave_usingUiAutomatorOnly() throws Exception {
232         startActivity();
233 
234         // Set service.
235         enableService();
236 
237         // Set expectations.
238         sReplier.addResponse(new CannedFillResponse.Builder()
239                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
240                 .addDataset(new CannedDataset.Builder()
241                         .setField(ID_INPUT, "id")
242                         .setField(ID_PASSWORD, "pass")
243                         .setPresentation(createPresentation("YO"))
244                         .build())
245                 .build());
246 
247         // Trigger autofill.
248         mUiBot.assertShownByRelativeId(ID_INPUT).click();
249         sReplier.getNextFillRequest();
250 
251         // Select dataset...
252         mUiBot.selectDataset("YO");
253 
254         // ...and assert autofilled values.
255         final UiObject2 input = mUiBot.assertShownByRelativeId(ID_INPUT);
256         final UiObject2 password = mUiBot.assertShownByRelativeId(ID_PASSWORD);
257 
258         assertWithMessage("wrong value for 'input'").that(input.getText()).isEqualTo("id");
259         // TODO: password field is shown as **** ; ideally we should assert it's a password
260         // field, but UiAutomator does not exposes that info.
261         final String visiblePassword = password.getText();
262         assertWithMessage("'password' should not be visible").that(visiblePassword)
263             .isNotEqualTo("pass");
264         assertWithMessage("wrong value for 'password'").that(visiblePassword).hasLength(4);
265 
266         // Trigger save...
267         input.setText("ID");
268         password.setText("PASS");
269         mUiBot.assertShownByRelativeId(ID_COMMIT).click();
270         mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
271 
272         // ... and assert results
273         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
274         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
275         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
276     }
277 
278     @Test
279     @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
testSave()280     public void testSave() throws Exception {
281         saveTest(false);
282     }
283 
284     @Presubmit
285     @Test
testSave_afterRotation()286     public void testSave_afterRotation() throws Exception {
287         assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
288         mUiBot.setScreenOrientation(UiBot.PORTRAIT);
289         try {
290             saveTest(true);
291         } finally {
292             try {
293                 mUiBot.setScreenOrientation(UiBot.PORTRAIT);
294                 cleanUpAfterScreenOrientationIsBackToPortrait();
295             } catch (Exception e) {
296                 mSafeCleanerRule.add(e);
297             }
298         }
299     }
300 
saveTest(boolean rotate)301     private void saveTest(boolean rotate) throws Exception {
302         startActivity();
303 
304         // Set service.
305         enableService();
306 
307         // Set expectations.
308         sReplier.addResponse(new CannedFillResponse.Builder()
309                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
310                 .build());
311 
312         // Trigger autofill.
313         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
314         sReplier.getNextFillRequest();
315 
316         // Trigger save.
317         mActivity.syncRunOnUiThread(() -> {
318             mActivity.mInput.setText("108");
319             mActivity.mCommit.performClick();
320         });
321         UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
322 
323         if (rotate) {
324             // After the device rotates, the input field get focus and generate a new session.
325             sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
326 
327             mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
328             saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
329         }
330 
331         // Save it...
332         mUiBot.saveForAutofill(saveUi, true);
333 
334         // ... and assert results
335         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
336         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
337     }
338 
339     /**
340      * Emulates an app dyanmically adding the password field after username is typed.
341      */
342     @Test
343     @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
testPartitionedSave()344     public void testPartitionedSave() throws Exception {
345         startActivity();
346 
347         // Set service.
348         enableService();
349 
350         // 1st request
351 
352         // Set expectations.
353         sReplier.addResponse(new CannedFillResponse.Builder()
354                 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_INPUT)
355                 .build());
356 
357         // Trigger autofill.
358         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
359         sReplier.getNextFillRequest();
360 
361         // Set 1st field but don't commit session
362         mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
363         mUiBot.assertSaveNotShowing();
364 
365         // 2nd request
366 
367         // Set expectations.
368         sReplier.addResponse(new CannedFillResponse.Builder()
369                 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
370                         ID_INPUT, ID_PASSWORD)
371                 .build());
372 
373         // Trigger autofill.
374         mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
375         sReplier.getNextFillRequest();
376 
377         // Trigger save.
378         mActivity.syncRunOnUiThread(() -> {
379             mActivity.mPassword.setText("42");
380             mActivity.mCommit.performClick();
381         });
382         final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME,
383                 SAVE_DATA_TYPE_PASSWORD);
384 
385         // Save it...
386         mUiBot.saveForAutofill(saveUi, true);
387 
388         // ... and assert results
389         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
390         assertThat(saveRequest.contexts.size()).isEqualTo(2);
391 
392         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
393         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
394     }
395 
396     /**
397      * Emulates an app using fragments to display username and password in 2 steps.
398      */
399     @Test
400     @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
testDelayedSave()401     public void testDelayedSave() throws Exception {
402         startActivity();
403 
404         // Set service.
405         enableService();
406 
407         // 1st fragment.
408 
409         // Set expectations.
410         sReplier.addResponse(new CannedFillResponse.Builder()
411                 .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE).build());
412 
413         // Trigger autofill.
414         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
415         sReplier.getNextFillRequest();
416 
417         // Trigger delayed save.
418         mActivity.syncRunOnUiThread(() -> {
419             mActivity.mInput.setText("108");
420             mActivity.mCommit.performClick();
421         });
422         mUiBot.assertSaveNotShowing();
423 
424         // 2nd fragment.
425 
426         // Set expectations.
427         sReplier.addResponse(new CannedFillResponse.Builder()
428                 // Must explicitly set visitor, otherwise setRequiredSavableIds() would get the
429                 // id from the 1st context
430                 .setVisitor((contexts, builder) -> {
431                     final AutofillId passwordId =
432                             findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD);
433                     final AutofillId inputId =
434                             findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
435                     builder.setSaveInfo(new SaveInfo.Builder(
436                             SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
437                             new AutofillId[] {inputId, passwordId})
438                             .build());
439                 })
440                 .build());
441 
442         // Trigger autofill on second "fragment"
443         mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
444         sReplier.getNextFillRequest();
445 
446         // Trigger delayed save.
447         mActivity.syncRunOnUiThread(() -> {
448             mActivity.mPassword.setText("42");
449             mActivity.mCommit.performClick();
450         });
451 
452         // Save it...
453         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
454 
455         // ... and assert results
456         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
457         assertThat(saveRequest.contexts.size()).isEqualTo(2);
458 
459         // Get username from 1st request.
460         final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
461         assertTextAndValue(findNodeByResourceId(structure1, ID_INPUT), "108");
462 
463         // Get password from 2nd request.
464         final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
465         assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "108");
466         assertTextAndValue(findNodeByResourceId(structure2, ID_PASSWORD), "42");
467     }
468 
469     @Presubmit
470     @Test
testSave_launchIntent()471     public void testSave_launchIntent() throws Exception {
472         startActivity();
473 
474         // Set service.
475         enableService();
476 
477         // Set expectations.
478         sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell"))
479                 .addResponse(new CannedFillResponse.Builder()
480                         .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
481                         .build());
482 
483         // Trigger autofill.
484         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
485         sReplier.getNextFillRequest();
486 
487         // Trigger save.
488         mActivity.syncRunOnUiThread(() -> {
489             mActivity.mInput.setText("108");
490             mActivity.mCommit.performClick();
491 
492             // Disable autofill so it's not triggered again after WelcomeActivity finishes
493             // and mActivity is resumed (with focus on mInput) after the session is closed
494             mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
495         });
496 
497         // Save it...
498         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
499         sReplier.getNextSaveRequest();
500 
501         // ... and assert activity was launched
502         WelcomeActivity.assertShowing(mUiBot, "Saved by the bell");
503     }
504 
505     @Presubmit
506     @Test
testSaveThenStartNewSessionRightAwayShouldKeepSaveUi()507     public void testSaveThenStartNewSessionRightAwayShouldKeepSaveUi() throws Exception {
508         startActivity();
509 
510         // Set service.
511         enableService();
512 
513         // Set expectations.
514         sReplier.addResponse(new CannedFillResponse.Builder()
515                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
516                 .build());
517 
518         // Trigger autofill.
519         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
520         sReplier.getNextFillRequest();
521 
522         // Trigger save.
523         mActivity.syncRunOnUiThread(() -> {
524             mActivity.mInput.setText("108");
525             mActivity.mCommit.performClick();
526         });
527 
528         // Make sure Save UI for 1st session was shown....
529         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
530 
531         // Start new Activity to have a new autofill session
532         startActivityOnNewTask(LoginActivity.class);
533 
534         // Make sure LoginActivity started...
535         mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
536 
537         // Set expectations.
538         sReplier.addResponse(new CannedFillResponse.Builder()
539                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
540                 .addDataset(new CannedDataset.Builder()
541                         .setField(ID_USERNAME, "id")
542                         .setField(ID_PASSWORD, "pwd")
543                         .setPresentation(createPresentation("YO"))
544                         .build())
545                 .build());
546         // Trigger fill request on the LoginActivity
547         final LoginActivity act = LoginActivity.getCurrentActivity();
548         act.syncRunOnUiThread(() -> act.forceAutofillOnUsername());
549         sReplier.getNextFillRequest();
550 
551         // Make sure Fill UI is not shown. And Save UI for 1st session was still shown.
552         mUiBot.assertNoDatasetsEver();
553         sReplier.assertNoUnhandledFillRequests();
554         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
555 
556         mUiBot.waitForIdle();
557         // Trigger dismiss Save UI
558         mUiBot.pressBack();
559 
560         // Make sure Save UI was not shown....
561         mUiBot.assertSaveNotShowing();
562         // Make sure Fill UI is shown.
563         mUiBot.assertDatasets("YO");
564     }
565 
566     @Presubmit
567     @Test
testCloseSaveUiThenStartNewSessionRightAway()568     public void testCloseSaveUiThenStartNewSessionRightAway() throws Exception {
569         startActivity();
570 
571         // Set service.
572         enableService();
573 
574         // Set expectations.
575         sReplier.addResponse(new CannedFillResponse.Builder()
576                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
577                 .build());
578 
579         // Trigger autofill.
580         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
581         sReplier.getNextFillRequest();
582 
583         // Trigger save.
584         mActivity.syncRunOnUiThread(() -> {
585             mActivity.mInput.setText("108");
586             mActivity.mCommit.performClick();
587         });
588 
589         // Make sure Save UI for 1st session was shown....
590         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
591 
592         // Trigger dismiss Save UI
593         mUiBot.pressBack();
594 
595         // Make sure Save UI for 1st session was canceled.
596         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
597 
598         // ...then start the new session right away (without finishing the activity).
599         // Set expectations.
600         sReplier.addResponse(new CannedFillResponse.Builder()
601                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
602                 .addDataset(new CannedDataset.Builder()
603                         .setField(ID_INPUT, "id")
604                         .setPresentation(createPresentation("YO"))
605                         .build())
606                 .build());
607         mActivity.syncRunOnUiThread(() -> {
608             mActivity.mInput.setText("");
609             mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
610         });
611         sReplier.getNextFillRequest();
612 
613         // Make sure Fill UI is shown.
614         mUiBot.assertDatasets("YO");
615     }
616 
617     @Presubmit
618     @Test
testSaveWithParcelableOnClientState()619     public void testSaveWithParcelableOnClientState() throws Exception {
620         startActivity();
621 
622         // Set service.
623         enableService();
624 
625         // Set expectations.
626         final AutofillId id = new AutofillId(42);
627         final Bundle clientState = new Bundle();
628         clientState.putParcelable("id", id);
629         clientState.putParcelable("my_id", new MyAutofillId(id));
630         sReplier.addResponse(new CannedFillResponse.Builder()
631                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
632                 .setExtras(clientState)
633                 .build());
634 
635         // Trigger autofill.
636         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
637         sReplier.getNextFillRequest();
638 
639         // Trigger save.
640         mActivity.syncRunOnUiThread(() -> {
641             mActivity.mInput.setText("108");
642             mActivity.mCommit.performClick();
643         });
644         UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
645 
646         // Save it...
647         mUiBot.saveForAutofill(saveUi, true);
648 
649         // ... and assert results
650         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
651         assertMyClientState(saveRequest.data);
652 
653         // Also check fillevent history
654         final FillEventHistory history = InstrumentedAutoFillService.getFillEventHistory(1);
655         @SuppressWarnings("deprecation")
656         final Bundle deprecatedState = history.getClientState();
657         assertMyClientState(deprecatedState);
658         assertMyClientState(history.getEvents().get(0).getClientState());
659     }
660 
assertMyClientState(Bundle data)661     private void assertMyClientState(Bundle data) {
662         // Must set proper classpath before reading the data, otherwise Bundle will use it's
663         // on class classloader, which is the framework's.
664         data.setClassLoader(getClass().getClassLoader());
665 
666         final AutofillId expectedId = new AutofillId(42);
667         final AutofillId actualId = data.getParcelable("id");
668         assertThat(actualId).isEqualTo(expectedId);
669         final MyAutofillId actualMyId = data.getParcelable("my_id");
670         assertThat(actualMyId).isEqualTo(new MyAutofillId(expectedId));
671     }
672 
673     @Presubmit
674     @Test
testCancelPreventsSaveUiFromShowing()675     public void testCancelPreventsSaveUiFromShowing() throws Exception {
676         startActivity();
677 
678         // Set service.
679         enableService();
680 
681         // Set expectations.
682         sReplier.addResponse(new CannedFillResponse.Builder()
683                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
684                 .build());
685 
686         // Trigger autofill.
687         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
688         sReplier.getNextFillRequest();
689 
690         // Cancel session.
691         mActivity.getAutofillManager().cancel();
692 
693         // Trigger save.
694         mActivity.syncRunOnUiThread(() -> {
695             mActivity.mInput.setText("108");
696             mActivity.mCommit.performClick();
697         });
698 
699         // Assert it's not showing.
700         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
701     }
702 
703     @Presubmit
704     @Test
testDismissSave_byTappingBack()705     public void testDismissSave_byTappingBack() throws Exception {
706         startActivity();
707         dismissSaveTest(DismissType.BACK_BUTTON);
708     }
709 
710     @Test
testDismissSave_byTappingHome()711     public void testDismissSave_byTappingHome() throws Exception {
712         startActivity();
713         dismissSaveTest(DismissType.HOME_BUTTON);
714     }
715 
716     @Presubmit
717     @Test
testDismissSave_byTouchingOutside()718     public void testDismissSave_byTouchingOutside() throws Exception {
719         startActivity();
720         dismissSaveTest(DismissType.TOUCH_OUTSIDE);
721     }
722 
723     @Presubmit
724     @Test
testDismissSave_byFocusingOutside()725     public void testDismissSave_byFocusingOutside() throws Exception {
726         startActivity();
727         dismissSaveTest(DismissType.FOCUS_OUTSIDE);
728     }
729 
dismissSaveTest(DismissType dismissType)730     private void dismissSaveTest(DismissType dismissType) throws Exception {
731         // Set service.
732         enableService();
733 
734         // Set expectations.
735         sReplier.addResponse(new CannedFillResponse.Builder()
736                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
737                 .build());
738 
739         // Trigger autofill.
740         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
741         sReplier.getNextFillRequest();
742 
743         // Trigger save.
744         mActivity.syncRunOnUiThread(() -> {
745             mActivity.mInput.setText("108");
746             mActivity.mCommit.performClick();
747         });
748         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
749 
750         // Then make sure it goes away when user doesn't want it..
751         switch (dismissType) {
752             case BACK_BUTTON:
753                 mUiBot.pressBack();
754                 break;
755             case HOME_BUTTON:
756                 mUiBot.pressHome();
757                 break;
758             case TOUCH_OUTSIDE:
759                 mUiBot.assertShownByText(TEXT_LABEL).click();
760                 break;
761             case FOCUS_OUTSIDE:
762                 mActivity.syncRunOnUiThread(() -> mActivity.mLabel.requestFocus());
763                 mUiBot.assertShownByText(TEXT_LABEL).click();
764                 break;
765             default:
766                 throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
767         }
768         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
769     }
770 
771     @Presubmit
772     @Test
testTapHomeWhileDatasetPickerUiIsShowing()773     public void testTapHomeWhileDatasetPickerUiIsShowing() throws Exception {
774         startActivity();
775         enableService();
776         final MyAutofillCallback callback = mActivity.registerCallback();
777 
778         // Set expectations.
779         sReplier.addResponse(new CannedFillResponse.Builder()
780                 .addDataset(new CannedDataset.Builder()
781                         .setField(ID_INPUT, "id")
782                         .setField(ID_PASSWORD, "pass")
783                         .setPresentation(createPresentation("YO"))
784                         .build())
785                 .build());
786 
787         // Trigger autofill.
788         mUiBot.assertShownByRelativeId(ID_INPUT).click();
789         sReplier.getNextFillRequest();
790         mUiBot.assertDatasets("YO");
791         callback.assertUiShownEvent(mActivity.mInput);
792 
793         // Go home, you are drunk!
794         mUiBot.pressHome();
795         mUiBot.assertNoDatasets();
796         callback.assertUiHiddenEvent(mActivity.mInput);
797 
798         // Set expectations.
799         sReplier.addResponse(new CannedFillResponse.Builder()
800                 .addDataset(new CannedDataset.Builder()
801                         .setField(ID_INPUT, "id")
802                         .setField(ID_PASSWORD, "pass")
803                         .setPresentation(createPresentation("YO2"))
804                         .build())
805                 .build());
806 
807         // Switch back to the activity.
808         restartActivity();
809         mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
810         sReplier.getNextFillRequest();
811         final UiObject2 datasetPicker = mUiBot.assertDatasets("YO2");
812         callback.assertUiShownEvent(mActivity.mInput);
813 
814         // Now autofill it.
815         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
816         mUiBot.selectDataset(datasetPicker, "YO2");
817         autofillExpecation.assertAutoFilled();
818     }
819 
820     @Presubmit
821     @Test
testTapHomeWhileSaveUiIsShowing()822     public void testTapHomeWhileSaveUiIsShowing() throws Exception {
823         startActivity();
824         enableService();
825 
826         // Set expectations.
827         sReplier.addResponse(new CannedFillResponse.Builder()
828                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
829                 .build());
830 
831         // Trigger autofill.
832         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
833         sReplier.getNextFillRequest();
834         mUiBot.assertNoDatasetsEver();
835 
836         // Trigger save, but don't tap it.
837         mActivity.syncRunOnUiThread(() -> {
838             mActivity.mInput.setText("108");
839             mActivity.mCommit.performClick();
840         });
841         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
842 
843         // Go home, you are drunk!
844         mUiBot.pressHome();
845         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
846 
847         // Prepare the response for the next session, which will be automatically triggered
848         // when the activity is brought back.
849         sReplier.addResponse(new CannedFillResponse.Builder()
850                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
851                 .addDataset(new CannedDataset.Builder()
852                         .setField(ID_INPUT, "id")
853                         .setField(ID_PASSWORD, "pass")
854                         .setPresentation(createPresentation("YO"))
855                         .build())
856                 .build());
857 
858         // Switch back to the activity.
859         restartActivity();
860         mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
861         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
862         sReplier.getNextFillRequest();
863         mUiBot.assertNoDatasetsEver();
864 
865         // Trigger and select UI.
866         mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
867         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
868         mUiBot.selectDataset("YO");
869 
870         // Assert it.
871         autofillExpecation.assertAutoFilled();
872     }
873 
874     @Override
saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)875     protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
876             throws Exception {
877         startActivity();
878         // Set service.
879         enableService();
880 
881         // Set expectations.
882         sReplier.addResponse(new CannedFillResponse.Builder()
883                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
884                 .setSaveInfoVisitor((contexts, builder) -> builder
885                         .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
886                 .build());
887 
888         // Trigger autofill.
889         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
890         sReplier.getNextFillRequest();
891 
892         // Trigger save.
893         mActivity.syncRunOnUiThread(() -> {
894             mActivity.mInput.setText("108");
895             mActivity.mCommit.performClick();
896         });
897         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
898 
899         // Tap the link.
900         tapSaveUiLink(saveUi);
901 
902         // Make sure new activity is shown...
903         WelcomeActivity.assertShowingDefaultMessage(mUiBot);
904         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
905 
906         // .. then do something to return to previous activity...
907         switch (type) {
908             case ROTATE_THEN_TAP_BACK_BUTTON:
909                 // After the device rotates, the input field get focus and generate a new session.
910                 sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
911 
912                 mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
913                 WelcomeActivity.assertShowingDefaultMessage(mUiBot);
914                 // not breaking on purpose
915             case TAP_BACK_BUTTON:
916                 // ..then go back and save it.
917                 mUiBot.pressBack();
918                 break;
919             case FINISH_ACTIVITY:
920                 // ..then finishes it.
921                 WelcomeActivity.finishIt();
922                 break;
923             default:
924                 throw new IllegalArgumentException("invalid type: " + type);
925         }
926         // Make sure previous activity is back...
927         mUiBot.assertShownByRelativeId(ID_INPUT);
928 
929         // ... and tap save.
930         final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
931         mUiBot.saveForAutofill(newSaveUi, true);
932 
933         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
934         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
935     }
936 
937     @Override
cleanUpAfterScreenOrientationIsBackToPortrait()938     protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
939         sReplier.getNextFillRequest();
940     }
941 
942     @Override
tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action, boolean manualRequest)943     protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
944             boolean manualRequest) throws Exception {
945         startActivity();
946         // Set service.
947         enableService();
948 
949         // Set expectations.
950         sReplier.addResponse(new CannedFillResponse.Builder()
951                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
952                 .setSaveInfoVisitor((contexts, builder) -> builder
953                         .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
954                 .build());
955 
956         // Trigger autofill.
957         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
958         sReplier.getNextFillRequest();
959 
960         // Trigger save.
961         mActivity.syncRunOnUiThread(() -> {
962             mActivity.mInput.setText("108");
963             mActivity.mCommit.performClick();
964         });
965         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
966 
967         // Tap the link.
968         tapSaveUiLink(saveUi);
969 
970         // Make sure new activity is shown.
971         WelcomeActivity.assertShowingDefaultMessage(mUiBot);
972         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
973 
974         // Tap back to restore the Save UI...
975         mUiBot.pressBack();
976         // Make sure previous activity is back...
977         mUiBot.assertShownByRelativeId(ID_LABEL);
978 
979         // ...but don't tap it...
980         final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
981 
982         // ...instead, do something to dismiss it:
983         switch (action) {
984             case TOUCH_OUTSIDE:
985                 mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
986                 break;
987             case TAP_NO_ON_SAVE_UI:
988                 mUiBot.saveForAutofill(saveUi2, false);
989                 break;
990             case TAP_YES_ON_SAVE_UI:
991                 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
992                 final SaveRequest saveRequest = sReplier.getNextSaveRequest();
993                 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
994                 break;
995             default:
996                 throw new IllegalArgumentException("invalid action: " + action);
997         }
998         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
999 
1000         // Now triggers a new session and do business as usual...
1001         sReplier.addResponse(new CannedFillResponse.Builder()
1002                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1003                 .build());
1004 
1005         // Trigger autofill.
1006         if (manualRequest) {
1007             mActivity.syncRunOnUiThread(
1008                     () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput));
1009         } else {
1010             mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
1011         }
1012 
1013         sReplier.getNextFillRequest();
1014 
1015         // Trigger save.
1016         mActivity.syncRunOnUiThread(() -> {
1017             mActivity.mInput.setText("42");
1018             mActivity.mCommit.performClick();
1019         });
1020 
1021         // Save it...
1022         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
1023 
1024         // ... and assert results
1025         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
1026         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
1027     }
1028 
1029     @Override
saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)1030     protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
1031             throws Exception {
1032         startActivity(false);
1033         // Set service.
1034         enableService();
1035 
1036         // Set expectations.
1037         sReplier.addResponse(new CannedFillResponse.Builder()
1038                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1039                 .setSaveInfoVisitor((contexts, builder) -> builder
1040                         .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
1041                 .build());
1042 
1043         // Trigger autofill.
1044         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1045         sReplier.getNextFillRequest();
1046 
1047         // Trigger save.
1048         mActivity.syncRunOnUiThread(() -> {
1049             mActivity.mInput.setText("108");
1050             mActivity.mCommit.performClick();
1051         });
1052         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
1053 
1054         // Tap the link.
1055         tapSaveUiLink(saveUi);
1056         // Make sure new activity is shown...
1057         WelcomeActivity.assertShowingDefaultMessage(mUiBot);
1058         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
1059 
1060         switch (type) {
1061             case LAUNCH_PREVIOUS_ACTIVITY:
1062                 startActivityOnNewTask(SimpleSaveActivity.class);
1063                 break;
1064             case LAUNCH_NEW_ACTIVITY:
1065                 // Launch a 3rd activity...
1066                 startActivityOnNewTask(LoginActivity.class);
1067                 mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
1068                 // ...then go back
1069                 mUiBot.pressBack();
1070                 break;
1071             default:
1072                 throw new IllegalArgumentException("invalid type: " + type);
1073         }
1074         // Make sure right activity is showing
1075         mUiBot.assertShownByRelativeId(ID_INPUT, Timeouts.ACTIVITY_RESURRECTION);
1076 
1077         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
1078     }
1079 
1080     @Presubmit
1081     @Test
1082     @AppModeFull(reason = "Service-specific test")
testSelectedDatasetsAreSentOnSaveRequest()1083     public void testSelectedDatasetsAreSentOnSaveRequest() throws Exception {
1084         startActivity();
1085 
1086         // Set service.
1087         enableService();
1088 
1089         // Set expectations.
1090         sReplier.addResponse(new CannedFillResponse.Builder()
1091                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
1092                 // Added on reversed order on purpose
1093                 .addDataset(new CannedDataset.Builder()
1094                         .setId("D2")
1095                         .setField(ID_INPUT, "id again")
1096                         .setField(ID_PASSWORD, "pass")
1097                         .setPresentation(createPresentation("D2"))
1098                         .build())
1099                 .addDataset(new CannedDataset.Builder()
1100                         .setId("D1")
1101                         .setField(ID_INPUT, "id")
1102                         .setPresentation(createPresentation("D1"))
1103                         .build())
1104                 .build());
1105 
1106         // Trigger autofill.
1107         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1108         sReplier.getNextFillRequest();
1109 
1110         // Select 1st dataset.
1111         final FillExpectation autofillExpecation1 = mActivity.expectAutoFill("id");
1112         final UiObject2 picker1 = mUiBot.assertDatasets("D2", "D1");
1113         mUiBot.selectDataset(picker1, "D1");
1114         autofillExpecation1.assertAutoFilled();
1115 
1116         // Select 2nd dataset.
1117         mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
1118         final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass");
1119         final UiObject2 picker2 = mUiBot.assertDatasets("D2");
1120         mUiBot.selectDataset(picker2, "D2");
1121         autofillExpecation2.assertAutoFilled();
1122 
1123         mActivity.syncRunOnUiThread(() -> {
1124             mActivity.mInput.setText("ID");
1125             mActivity.mPassword.setText("PASS");
1126             mActivity.mCommit.performClick();
1127         });
1128         final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
1129 
1130         // Save it...
1131         mUiBot.saveForAutofill(saveUi, true);
1132 
1133         // ... and assert results
1134         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
1135         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
1136         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
1137         assertThat(saveRequest.datasetIds).containsExactly("D1", "D2").inOrder();
1138     }
1139 
1140     @Override
tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()1141     protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
1142             throws Exception {
1143         // Prepare activity.
1144         startActivity();
1145         mActivity.syncRunOnUiThread(() -> mActivity.mInput.getRootView()
1146                 .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS)
1147         );
1148 
1149         // Set service.
1150         enableService();
1151 
1152         // Set expectations.
1153         sReplier.addResponse(new CannedFillResponse.Builder()
1154                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1155                 .setSaveInfoVisitor((contexts, builder) -> builder
1156                         .setCustomDescription(
1157                                 newCustomDescription(TrampolineWelcomeActivity.class)))
1158                 .build());
1159 
1160         // Trigger autofill.
1161         mActivity.syncRunOnUiThread(
1162                 () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput));
1163         sReplier.getNextFillRequest();
1164 
1165         // Trigger save.
1166         mActivity.syncRunOnUiThread(() -> {
1167             mActivity.mInput.setText("108");
1168             mActivity.mCommit.performClick();
1169         });
1170         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
1171 
1172         // Tap the link.
1173         tapSaveUiLink(saveUi);
1174 
1175         // Make sure new activity is shown...
1176         WelcomeActivity.assertShowingDefaultMessage(mUiBot);
1177 
1178         // Save UI should be showing as well, since Trampoline finished.
1179         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
1180 
1181         // Dismiss Save Dialog
1182         mUiBot.pressBack();
1183         // Go back and make sure it's showing the right activity.
1184         mUiBot.pressBack();
1185         mUiBot.assertShownByRelativeId(ID_LABEL);
1186 
1187         // Now start a new session.
1188         sReplier.addResponse(new CannedFillResponse.Builder()
1189                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
1190                 .build());
1191 
1192         // Trigger autofill on password
1193         mActivity.syncRunOnUiThread(
1194                 () -> mActivity.getAutofillManager().requestAutofill(mActivity.mPassword));
1195         sReplier.getNextFillRequest();
1196 
1197         mActivity.syncRunOnUiThread(() -> {
1198             mActivity.mPassword.setText("42");
1199             mActivity.mCommit.performClick();
1200         });
1201         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
1202         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
1203         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
1204         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
1205     }
1206 
1207     @Presubmit
1208     @Test
testSanitizeOnSaveWhenAppChangeValues()1209     public void testSanitizeOnSaveWhenAppChangeValues() throws Exception {
1210         startActivity();
1211 
1212         // Set listeners that will change the saved value
1213         new AntiTrimmerTextWatcher(mActivity.mInput);
1214         new AntiTrimmerTextWatcher(mActivity.mPassword);
1215 
1216         // Set service.
1217         enableService();
1218 
1219         // Set expectations.
1220         sReplier.addResponse(new CannedFillResponse.Builder()
1221                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1222                 .setSaveInfoVisitor((contexts, builder) -> {
1223                     final FillContext context = contexts.get(0);
1224                     final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
1225                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
1226                     builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
1227                             passwordId);
1228                 })
1229                 .build());
1230 
1231         // Trigger autofill.
1232         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1233         sReplier.getNextFillRequest();
1234 
1235         // Trigger save.
1236         mActivity.syncRunOnUiThread(() -> {
1237             mActivity.mInput.setText("id");
1238             mActivity.mPassword.setText("pass");
1239             mActivity.mCommit.performClick();
1240         });
1241 
1242         // Save it...
1243         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
1244 
1245         // ... and assert results
1246         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
1247         assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
1248         assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
1249     }
1250 
1251     @Test
1252     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
testSanitizeOnSaveNoChange()1253     public void testSanitizeOnSaveNoChange() throws Exception {
1254         startActivity();
1255 
1256         // Set service.
1257         enableService();
1258 
1259         // Set expectations.
1260         sReplier.addResponse(new CannedFillResponse.Builder()
1261                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1262                 .setOptionalSavableIds(ID_PASSWORD)
1263                 .setSaveInfoVisitor((contexts, builder) -> {
1264                     final FillContext context = contexts.get(0);
1265                     final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
1266                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
1267                     builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
1268                             passwordId);
1269                 })
1270                 .build());
1271 
1272         // Trigger autofill.
1273         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1274         sReplier.getNextFillRequest();
1275         mUiBot.assertNoDatasetsEver();
1276 
1277         // Trigger save.
1278         mActivity.syncRunOnUiThread(() -> {
1279             mActivity.mInput.setText("#id#");
1280             mActivity.mPassword.setText("#pass#");
1281             mActivity.mCommit.performClick();
1282         });
1283 
1284         // Save it...
1285         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
1286 
1287         // ... and assert results
1288         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
1289         assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
1290         assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
1291     }
1292 
1293     @Test
1294     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
testDontSaveWhenSanitizedValueForRequiredFieldDidntChange()1295     public void testDontSaveWhenSanitizedValueForRequiredFieldDidntChange() throws Exception {
1296         startActivity();
1297 
1298         // Set listeners that will change the saved value
1299         new AntiTrimmerTextWatcher(mActivity.mInput);
1300         new AntiTrimmerTextWatcher(mActivity.mPassword);
1301 
1302         // Set service.
1303         enableService();
1304 
1305         // Set expectations.
1306         sReplier.addResponse(new CannedFillResponse.Builder()
1307                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
1308                 .setSaveInfoVisitor((contexts, builder) -> {
1309                     final FillContext context = contexts.get(0);
1310                     final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
1311                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
1312                     builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
1313                             passwordId);
1314                 })
1315                 .addDataset(new CannedDataset.Builder()
1316                         .setField(ID_INPUT, "id")
1317                         .setField(ID_PASSWORD, "pass")
1318                         .setPresentation(createPresentation("YO"))
1319                         .build())
1320                 .build());
1321 
1322         // Trigger autofill.
1323         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1324         sReplier.getNextFillRequest();
1325 
1326         mActivity.syncRunOnUiThread(() -> {
1327             mActivity.mInput.setText("id");
1328             mActivity.mPassword.setText("pass");
1329             mActivity.mCommit.performClick();
1330         });
1331 
1332         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
1333     }
1334 
1335     @Test
1336     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
testDontSaveWhenSanitizedValueForOptionalFieldDidntChange()1337     public void testDontSaveWhenSanitizedValueForOptionalFieldDidntChange() throws Exception {
1338         startActivity();
1339 
1340         // Set service.
1341         enableService();
1342 
1343         // Set expectations.
1344         sReplier.addResponse(new CannedFillResponse.Builder()
1345                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1346                 .setOptionalSavableIds(ID_PASSWORD)
1347                 .setSaveInfoVisitor((contexts, builder) -> {
1348                     final FillContext context = contexts.get(0);
1349                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
1350                     builder.addSanitizer(new TextValueSanitizer(Pattern.compile("(pass) "), "$1"),
1351                             passwordId);
1352                 })
1353                 .addDataset(new CannedDataset.Builder()
1354                         .setField(ID_INPUT, "id")
1355                         .setField(ID_PASSWORD, "pass")
1356                         .setPresentation(createPresentation("YO"))
1357                         .build())
1358                 .build());
1359 
1360         // Trigger autofill.
1361         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1362         sReplier.getNextFillRequest();
1363 
1364         mActivity.syncRunOnUiThread(() -> {
1365             mActivity.mInput.setText("id");
1366             mActivity.mPassword.setText("#pass#");
1367             mActivity.mCommit.performClick();
1368         });
1369 
1370         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
1371     }
1372 
1373     @Test
1374     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
testDontSaveWhenRequiredFieldFailedSanitization()1375     public void testDontSaveWhenRequiredFieldFailedSanitization() throws Exception {
1376         startActivity();
1377 
1378         // Set service.
1379         enableService();
1380 
1381         // Set expectations.
1382         sReplier.addResponse(new CannedFillResponse.Builder()
1383                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
1384                 .setSaveInfoVisitor((contexts, builder) -> {
1385                     final FillContext context = contexts.get(0);
1386                     final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
1387                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
1388                     builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
1389                             inputId, passwordId);
1390                 })
1391                 .addDataset(new CannedDataset.Builder()
1392                         .setField(ID_INPUT, "#id#")
1393                         .setField(ID_PASSWORD, "#pass#")
1394                         .setPresentation(createPresentation("YO"))
1395                         .build())
1396                 .build());
1397 
1398         // Trigger autofill.
1399         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1400         sReplier.getNextFillRequest();
1401 
1402         mActivity.syncRunOnUiThread(() -> {
1403             mActivity.mInput.setText("id");
1404             mActivity.mPassword.setText("pass");
1405             mActivity.mCommit.performClick();
1406         });
1407 
1408         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
1409     }
1410 
1411     @Test
1412     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
testDontSaveWhenOptionalFieldFailedSanitization()1413     public void testDontSaveWhenOptionalFieldFailedSanitization() throws Exception {
1414         startActivity();
1415 
1416         // Set service.
1417         enableService();
1418 
1419         // Set expectations.
1420         sReplier.addResponse(new CannedFillResponse.Builder()
1421                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1422                 .setOptionalSavableIds(ID_PASSWORD)
1423                 .setSaveInfoVisitor((contexts, builder) -> {
1424                     final FillContext context = contexts.get(0);
1425                     final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
1426                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
1427                     builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
1428                             inputId, passwordId);
1429 
1430                 })
1431                 .addDataset(new CannedDataset.Builder()
1432                         .setField(ID_INPUT, "id")
1433                         .setField(ID_PASSWORD, "#pass#")
1434                         .setPresentation(createPresentation("YO"))
1435                         .build())
1436                 .build());
1437 
1438         // Trigger autofill.
1439         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1440         sReplier.getNextFillRequest();
1441 
1442         mActivity.syncRunOnUiThread(() -> {
1443             mActivity.mInput.setText("id");
1444             mActivity.mPassword.setText("pass");
1445             mActivity.mCommit.performClick();
1446         });
1447 
1448         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
1449     }
1450 
1451     @Test
1452     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
testDontSaveWhenInitialValueAndNoUserInputAndServiceDatasets()1453     public void testDontSaveWhenInitialValueAndNoUserInputAndServiceDatasets() throws Throwable {
1454         // Prepare activitiy.
1455         startActivity();
1456         mActivity.syncRunOnUiThread(() -> {
1457             // NOTE: input's value must be a subset of the dataset value, otherwise the dataset
1458             // picker is filtered out
1459             mActivity.mInput.setText("f");
1460             mActivity.mPassword.setText("b");
1461         });
1462 
1463         // Set service.
1464         enableService();
1465 
1466         // Set expectations.
1467         sReplier.addResponse(new CannedFillResponse.Builder()
1468                 .addDataset(new CannedDataset.Builder()
1469                         .setField(ID_INPUT, "foo")
1470                         .setField(ID_PASSWORD, "bar")
1471                         .setPresentation(createPresentation("The Dude"))
1472                         .build())
1473                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT, ID_PASSWORD).build());
1474 
1475         // Trigger auto-fill.
1476         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1477         sReplier.getNextFillRequest();
1478         mUiBot.assertDatasets("The Dude");
1479 
1480         // Trigger save.
1481         mActivity.getAutofillManager().commit();
1482 
1483         // Assert it's not showing.
1484         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
1485     }
1486 
1487     enum SetTextCondition {
1488         NORMAL,
1489         HAS_SESSION,
1490         EMPTY_TEXT,
1491         FOCUSED,
1492         NOT_IMPORTANT_FOR_AUTOFILL,
1493         INVISIBLE
1494     }
1495 
1496     /**
1497      * Tests scenario when a text field's text is set automatically, it should trigger autofill and
1498      * show Save UI.
1499      */
1500     @Presubmit
1501     @Test
testShowSaveUiWhenSetTextAutomatically()1502     public void testShowSaveUiWhenSetTextAutomatically() throws Exception {
1503         triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NORMAL);
1504     }
1505 
1506     /**
1507      * Tests scenario when a text field's text is set automatically, it should not trigger autofill
1508      * when there is an existing session.
1509      */
1510     @Presubmit
1511     @Test
testNotTriggerAutofillWhenSetTextWhileSessionExists()1512     public void testNotTriggerAutofillWhenSetTextWhileSessionExists() throws Exception {
1513         triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.HAS_SESSION);
1514     }
1515 
1516     /**
1517      * Tests scenario when a text field's text is set automatically, it should not trigger autofill
1518      * when the text is empty.
1519      */
1520     @Presubmit
1521     @Test
testNotTriggerAutofillWhenSetTextWhileEmptyText()1522     public void testNotTriggerAutofillWhenSetTextWhileEmptyText() throws Exception {
1523         triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.EMPTY_TEXT);
1524     }
1525 
1526     /**
1527      * Tests scenario when a text field's text is set automatically, it should not trigger autofill
1528      * when the field is focused.
1529      */
1530     @Presubmit
1531     @Test
testNotTriggerAutofillWhenSetTextWhileFocused()1532     public void testNotTriggerAutofillWhenSetTextWhileFocused() throws Exception {
1533         triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.FOCUSED);
1534     }
1535 
1536     /**
1537      * Tests scenario when a text field's text is set automatically, it should not trigger autofill
1538      * when the field is not important for autofill.
1539      */
1540     @Presubmit
1541     @Test
testNotTriggerAutofillWhenSetTextWhileNotImportantForAutofill()1542     public void testNotTriggerAutofillWhenSetTextWhileNotImportantForAutofill() throws Exception {
1543         triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NOT_IMPORTANT_FOR_AUTOFILL);
1544     }
1545 
1546     /**
1547      * Tests scenario when a text field's text is set automatically, it should not trigger autofill
1548      * when the field is not visible.
1549      */
1550     @Presubmit
1551     @Test
testNotTriggerAutofillWhenSetTextWhileInvisible()1552     public void testNotTriggerAutofillWhenSetTextWhileInvisible() throws Exception {
1553         triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.INVISIBLE);
1554     }
1555 
triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition condition)1556     private void triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition condition)
1557             throws Exception {
1558         startActivity();
1559 
1560         // Set service.
1561         enableService();
1562 
1563         // Set expectations.
1564         sReplier.addResponse(new CannedFillResponse.Builder()
1565                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1566                 .build());
1567 
1568         CharSequence inputText = "108";
1569 
1570         switch (condition) {
1571             case NORMAL:
1572                 // Nothing.
1573                 break;
1574             case HAS_SESSION:
1575                 mActivity.syncRunOnUiThread(() -> {
1576                     mActivity.mInput.setText("100");
1577                 });
1578                 sReplier.getNextFillRequest();
1579                 break;
1580             case EMPTY_TEXT:
1581                 inputText = "";
1582                 break;
1583             case FOCUSED:
1584                 mActivity.syncRunOnUiThread(() -> {
1585                     mActivity.mInput.requestFocus();
1586                 });
1587                 sReplier.getNextFillRequest();
1588                 break;
1589             case NOT_IMPORTANT_FOR_AUTOFILL:
1590                 mActivity.syncRunOnUiThread(() -> {
1591                     mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
1592                 });
1593                 break;
1594             case INVISIBLE:
1595                 mActivity.syncRunOnUiThread(() -> {
1596                     mActivity.mInput.setVisibility(View.INVISIBLE);
1597                 });
1598                 break;
1599             default:
1600                 throw new IllegalArgumentException("invalid condition: " + condition);
1601         }
1602 
1603         // Trigger autofill by setting text.
1604         final CharSequence text = inputText;
1605         mActivity.syncRunOnUiThread(() -> {
1606             mActivity.mInput.setText(text);
1607         });
1608 
1609         if (condition == SetTextCondition.NORMAL) {
1610             sReplier.getNextFillRequest();
1611 
1612             mActivity.syncRunOnUiThread(() -> {
1613                 mActivity.mInput.setText("100");
1614                 mActivity.mCommit.performClick();
1615             });
1616 
1617             mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
1618         } else {
1619             sReplier.assertOnFillRequestNotCalled();
1620         }
1621     }
1622 
1623     @Presubmit
1624     @Test
testExplicitlySaveButton()1625     public void testExplicitlySaveButton() throws Exception {
1626         explicitlySaveButtonTest(false, 0);
1627     }
1628 
1629     @Presubmit
1630     @Test
testExplicitlySaveButtonWhenAppClearFields()1631     public void testExplicitlySaveButtonWhenAppClearFields() throws Exception {
1632         explicitlySaveButtonTest(true, 0);
1633     }
1634 
1635     @Presubmit
1636     @Test
testExplicitlySaveButtonOnly()1637     public void testExplicitlySaveButtonOnly() throws Exception {
1638         explicitlySaveButtonTest(false, SaveInfo.FLAG_DONT_SAVE_ON_FINISH);
1639     }
1640 
1641     /**
1642      * Tests scenario where service explicitly indicates which button is used to save.
1643      */
explicitlySaveButtonTest(boolean clearFieldsOnSubmit, int flags)1644     private void explicitlySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception {
1645         final boolean testBitmap = false;
1646         startActivity();
1647         mActivity.setAutoCommit(false);
1648         mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit);
1649 
1650         // Set service.
1651         enableService();
1652 
1653         // Set expectations.
1654         sReplier.addResponse(new CannedFillResponse.Builder()
1655                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1656                 .setSaveTriggerId(mActivity.mCommit.getAutofillId())
1657                 .setSaveInfoFlags(flags)
1658                 .build());
1659 
1660         // Trigger autofill.
1661         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1662         sReplier.getNextFillRequest();
1663 
1664         // Trigger save.
1665         mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
1666 
1667         // Take a screenshot to make sure button doesn't disappear.
1668         final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
1669         assertThat(commitBefore.toUpperCase()).isEqualTo("COMMIT");
1670         // Disable unnecessary screenshot tests as takeScreenshot() fails on some device.
1671 
1672         final Bitmap screenshotBefore = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
1673                 : null;
1674 
1675         // Save it...
1676         mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
1677         final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
1678         mUiBot.saveForAutofill(saveUi, true);
1679 
1680         // Make sure save button is showning (it was removed on earlier versions of the feature)
1681         final String commitAfter = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
1682         assertThat(commitAfter.toUpperCase()).isEqualTo("COMMIT");
1683         final Bitmap screenshotAfter = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
1684                 : null;
1685 
1686         // ... and assert results
1687         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
1688         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
1689 
1690         if (testBitmap) {
1691             Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter);
1692         }
1693     }
1694 
1695     @Override
tapLinkAfterUpdateAppliedTest(boolean updateLinkView)1696     protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
1697         startActivity();
1698         // Set service.
1699         enableService();
1700 
1701         // Set expectations.
1702         sReplier.addResponse(new CannedFillResponse.Builder()
1703                 .setSaveInfoVisitor((contexts, builder) -> {
1704                     // Set response with custom description
1705                     final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
1706                     final CustomDescription.Builder customDescription =
1707                             newCustomDescriptionBuilder(WelcomeActivity.class);
1708                     final RemoteViews update = newTemplate();
1709                     if (updateLinkView) {
1710                         update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
1711                     } else {
1712                         update.setCharSequence(R.id.static_text, "setText", "ME!");
1713                     }
1714                     Validator validCondition = new RegexValidator(id, Pattern.compile(".*"));
1715                     customDescription.batchUpdate(validCondition,
1716                             new BatchUpdates.Builder().updateTemplate(update).build());
1717 
1718                     builder.setCustomDescription(customDescription.build());
1719                 })
1720                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1721                 .build());
1722 
1723         // Trigger autofill.
1724         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1725         sReplier.getNextFillRequest();
1726         // Trigger save.
1727         mActivity.syncRunOnUiThread(() -> {
1728             mActivity.mInput.setText("108");
1729             mActivity.mCommit.performClick();
1730         });
1731         final UiObject2 saveUi;
1732         if (updateLinkView) {
1733             saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC, "TAP ME IF YOU CAN");
1734         } else {
1735             saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
1736             final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
1737             assertThat(changed.getText()).isEqualTo("ME!");
1738         }
1739 
1740         // Tap the link.
1741         tapSaveUiLink(saveUi);
1742 
1743         // Make sure new activity is shown...
1744         WelcomeActivity.assertShowingDefaultMessage(mUiBot);
1745         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
1746     }
1747 
1748     enum DescriptionType {
1749         SUCCINCT,
1750         CUSTOM,
1751     }
1752 
1753     /**
1754      * Tests scenarios when user taps a span in the custom description, then the new activity
1755      * finishes:
1756      * the Save UI should have been restored.
1757      */
1758     @Presubmit
1759     @Test
1760     @AppModeFull(reason = "No real use case for instant mode af service")
testTapUrlSpanOnCustomDescription_thenTapBack()1761     public void testTapUrlSpanOnCustomDescription_thenTapBack() throws Exception {
1762         saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
1763                 ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
1764     }
1765 
1766     /**
1767      * Tests scenarios when user taps a span in the succinct description, then the new activity
1768      * finishes:
1769      * the Save UI should have been restored.
1770      */
1771     @Presubmit
1772     @Test
1773     @AppModeFull(reason = "No real use case for instant mode af service")
testTapUrlSpanOnSuccinctDescription_thenTapBack()1774     public void testTapUrlSpanOnSuccinctDescription_thenTapBack() throws Exception {
1775         saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
1776                 ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
1777     }
1778 
1779     /**
1780      * Tests scenarios when user taps a span in the custom description, then the new activity
1781      * starts an another activity then it finishes:
1782      * the Save UI should have been restored.
1783      */
1784     @Presubmit
1785     @Test
1786     @AppModeFull(reason = "No real use case for instant mode af service")
testTapUrlSpanOnCustomDescription_forwardAnotherActivityThenTapBack()1787     public void testTapUrlSpanOnCustomDescription_forwardAnotherActivityThenTapBack()
1788             throws Exception {
1789         saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
1790                 ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
1791     }
1792 
1793     /**
1794      * Tests scenarios when user taps a span in the succinct description, then the new activity
1795      * starts an another activity then it finishes:
1796      * the Save UI should have been restored.
1797      */
1798     @Presubmit
1799     @Test
1800     @AppModeFull(reason = "No real use case for instant mode af service")
testTapUrlSpanOnSuccinctDescription_forwardAnotherActivityThenTapBack()1801     public void testTapUrlSpanOnSuccinctDescription_forwardAnotherActivityThenTapBack()
1802             throws Exception {
1803         saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
1804                 ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
1805     }
1806 
1807     /**
1808      * Tests scenarios when user taps a span in the custom description, then the new activity
1809      * stops but does not finish:
1810      * the Save UI should have been restored.
1811      */
1812     @Presubmit
1813     @Test
1814     @AppModeFull(reason = "No real use case for instant mode af service")
testTapUrlSpanOnCustomDescription_tapBackWithoutFinish()1815     public void testTapUrlSpanOnCustomDescription_tapBackWithoutFinish() throws Exception {
1816         saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
1817                 ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
1818     }
1819 
1820     /**
1821      * Tests scenarios when user taps a span in the succinct description, then the new activity
1822      * stops but does not finish:
1823      * the Save UI should have been restored.
1824      */
1825     @Presubmit
1826     @Test
1827     @AppModeFull(reason = "No real use case for instant mode af service")
testTapUrlSpanOnSuccinctDescription_tapBackWithoutFinish()1828     public void testTapUrlSpanOnSuccinctDescription_tapBackWithoutFinish() throws Exception {
1829         saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
1830                 ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
1831     }
1832 
saveUiRestoredAfterTappingSpanTest( DescriptionType type, ViewActionActivity.ActivityCustomAction action)1833     private void saveUiRestoredAfterTappingSpanTest(
1834             DescriptionType type, ViewActionActivity.ActivityCustomAction action) throws Exception {
1835         startActivity();
1836         // Set service.
1837         enableService();
1838 
1839         switch (type) {
1840             case SUCCINCT:
1841                 // Set expectations with custom description.
1842                 sReplier.addResponse(new CannedFillResponse.Builder()
1843                         .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1844                         .setSaveDescription(newDescriptionWithUrlSpan(action.toString()))
1845                         .build());
1846                 break;
1847             case CUSTOM:
1848                 // Set expectations with custom description.
1849                 sReplier.addResponse(new CannedFillResponse.Builder()
1850                         .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
1851                         .setSaveInfoVisitor((contexts, builder) -> builder
1852                                 .setCustomDescription(
1853                                         newCustomDescriptionWithUrlSpan(action.toString())))
1854                         .build());
1855                 break;
1856             default:
1857                 throw new IllegalArgumentException("invalid type: " + type);
1858         }
1859 
1860         // Trigger autofill.
1861         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
1862         sReplier.getNextFillRequest();
1863 
1864         // Trigger save.
1865         mActivity.syncRunOnUiThread(() -> {
1866             mActivity.mInput.setText("108");
1867             mActivity.mCommit.performClick();
1868         });
1869         // Waits for the commit be processed
1870         mUiBot.waitForIdle();
1871 
1872         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
1873 
1874         // Tapping URLSpan.
1875         final URLSpan span = mUiBot.findFirstUrlSpanWithText("Here is URLSpan");
1876         mActivity.syncRunOnUiThread(() -> span.onClick(/* unused= */ null));
1877         // Waits for the save UI hided
1878         mUiBot.waitForIdle();
1879 
1880         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
1881 
1882         // .. check activity show up as expected
1883         switch (action) {
1884             case FAST_FORWARD_ANOTHER_ACTIVITY:
1885                 // Show up second activity.
1886                 SecondActivity.assertShowingDefaultMessage(mUiBot);
1887                 break;
1888             case NORMAL_ACTIVITY:
1889             case TAP_BACK_WITHOUT_FINISH:
1890                 // Show up view action handle activity.
1891                 ViewActionActivity.assertShowingDefaultMessage(mUiBot);
1892                 break;
1893             default:
1894                 throw new IllegalArgumentException("invalid action: " + action);
1895         }
1896 
1897         // ..then go back and save it.
1898         mUiBot.pressBack();
1899         // Waits for all UI processes to complete
1900         mUiBot.waitForIdle();
1901 
1902         // Make sure previous activity is back...
1903         mUiBot.assertShownByRelativeId(ID_INPUT);
1904 
1905         // ... and tap save.
1906         final UiObject2 newSaveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
1907         mUiBot.saveForAutofill(newSaveUi, /* yesDoIt= */ true);
1908 
1909         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
1910         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
1911 
1912         SecondActivity.finishIt();
1913         ViewActionActivity.finishIt();
1914     }
1915 
newCustomDescriptionWithUrlSpan(String action)1916     private CustomDescription newCustomDescriptionWithUrlSpan(String action) {
1917         final RemoteViews presentation = newTemplate();
1918         presentation.setTextViewText(R.id.custom_text, newDescriptionWithUrlSpan(action));
1919         return new CustomDescription.Builder(presentation).build();
1920     }
1921 
newDescriptionWithUrlSpan(String action)1922     private CharSequence newDescriptionWithUrlSpan(String action) {
1923         final String url = "autofillcts:" + action;
1924         final SpannableString ss = new SpannableString("Here is URLSpan");
1925         ss.setSpan(new URLSpan(url),
1926                 /* start= */ 8,  /* end= */ 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1927         return ss;
1928     }
1929 }
1930