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