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