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