Home | History | Annotate | Download | only in cts
      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;
     17 
     18 import static android.autofillservice.cts.AntiTrimmerTextWatcher.TRIMMER_PATTERN;
     19 import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
     20 import static android.autofillservice.cts.Helper.LARGE_STRING;
     21 import static android.autofillservice.cts.Helper.assertTextAndValue;
     22 import static android.autofillservice.cts.Helper.assertTextValue;
     23 import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
     24 import static android.autofillservice.cts.Helper.findNodeByResourceId;
     25 import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
     26 import static android.autofillservice.cts.SimpleSaveActivity.ID_COMMIT;
     27 import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
     28 import static android.autofillservice.cts.SimpleSaveActivity.ID_LABEL;
     29 import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
     30 import static android.autofillservice.cts.SimpleSaveActivity.TEXT_LABEL;
     31 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
     32 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
     33 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
     34 
     35 import static com.google.common.truth.Truth.assertThat;
     36 import static com.google.common.truth.Truth.assertWithMessage;
     37 
     38 import static org.junit.Assume.assumeTrue;
     39 
     40 import android.app.assist.AssistStructure;
     41 import android.app.assist.AssistStructure.ViewNode;
     42 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
     43 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
     44 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
     45 import android.autofillservice.cts.SimpleSaveActivity.FillExpectation;
     46 import android.content.Intent;
     47 import android.graphics.Bitmap;
     48 import android.os.Bundle;
     49 import android.platform.test.annotations.AppModeFull;
     50 import android.service.autofill.BatchUpdates;
     51 import android.service.autofill.CustomDescription;
     52 import android.service.autofill.FillContext;
     53 import android.service.autofill.FillEventHistory;
     54 import android.service.autofill.RegexValidator;
     55 import android.service.autofill.SaveInfo;
     56 import android.service.autofill.TextValueSanitizer;
     57 import android.service.autofill.Validator;
     58 import android.support.test.uiautomator.By;
     59 import android.support.test.uiautomator.UiObject2;
     60 import android.view.View;
     61 import android.view.autofill.AutofillId;
     62 import android.widget.RemoteViews;
     63 
     64 import org.junit.Test;
     65 import org.junit.rules.RuleChain;
     66 import org.junit.rules.TestRule;
     67 
     68 import java.util.regex.Pattern;
     69 
     70 public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase<SimpleSaveActivity> {
     71 
     72     private static final AutofillActivityTestRule<SimpleSaveActivity> sActivityRule =
     73             new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class, false);
     74 
     75     private static final AutofillActivityTestRule<WelcomeActivity> sWelcomeActivityRule =
     76             new AutofillActivityTestRule<WelcomeActivity>(WelcomeActivity.class, false);
     77 
     78     public SimpleSaveActivityTest() {
     79         super(SimpleSaveActivity.class);
     80     }
     81 
     82     @Override
     83     protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
     84         return sActivityRule;
     85     }
     86 
     87     @Override
     88     protected TestRule getMainTestRule() {
     89         return RuleChain.outerRule(sActivityRule).around(sWelcomeActivityRule);
     90     }
     91 
     92     private void restartActivity() {
     93         final Intent intent = new Intent(mContext.getApplicationContext(),
     94                 SimpleSaveActivity.class);
     95         intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
     96         mActivity.startActivity(intent);
     97     }
     98 
     99     @Test
    100     public void testAutoFillOneDatasetAndSave() throws Exception {
    101         startActivity();
    102 
    103         // Set service.
    104         enableService();
    105 
    106         // Set expectations.
    107         sReplier.addResponse(new CannedFillResponse.Builder()
    108                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
    109                 .addDataset(new CannedDataset.Builder()
    110                         .setField(ID_INPUT, "id")
    111                         .setField(ID_PASSWORD, "pass")
    112                         .setPresentation(createPresentation("YO"))
    113                         .build())
    114                 .build());
    115 
    116         // Trigger autofill.
    117         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    118         sReplier.getNextFillRequest();
    119 
    120         // Select dataset.
    121         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
    122         mUiBot.selectDataset("YO");
    123         autofillExpecation.assertAutoFilled();
    124 
    125         mActivity.syncRunOnUiThread(() -> {
    126             mActivity.mInput.setText("ID");
    127             mActivity.mPassword.setText("PASS");
    128             mActivity.mCommit.performClick();
    129         });
    130         final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
    131 
    132         // Save it...
    133         mUiBot.saveForAutofill(saveUi, true);
    134 
    135         // ... and assert results
    136         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
    137         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
    138         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
    139     }
    140 
    141     @Test
    142     @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
    143     public void testAutoFillOneDatasetAndSave_largeAssistStructure() throws Exception {
    144         startActivity();
    145 
    146         mActivity.syncRunOnUiThread(
    147                 () -> mActivity.mInput.setAutofillHints(LARGE_STRING, LARGE_STRING, LARGE_STRING));
    148 
    149         // Set service.
    150         enableService();
    151 
    152         // Set expectations.
    153         sReplier.addResponse(new CannedFillResponse.Builder()
    154                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
    155                 .addDataset(new CannedDataset.Builder()
    156                         .setField(ID_INPUT, "id")
    157                         .setField(ID_PASSWORD, "pass")
    158                         .setPresentation(createPresentation("YO"))
    159                         .build())
    160                 .build());
    161 
    162         // Trigger autofill.
    163         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    164         final FillRequest fillRequest = sReplier.getNextFillRequest();
    165         final ViewNode inputOnFill = findNodeByResourceId(fillRequest.structure, ID_INPUT);
    166         final String[] hintsOnFill = inputOnFill.getAutofillHints();
    167         // Cannot compare these large strings directly becauise it could cause ANR
    168         assertThat(hintsOnFill).hasLength(3);
    169         Helper.assertEqualsToLargeString(hintsOnFill[0]);
    170         Helper.assertEqualsToLargeString(hintsOnFill[1]);
    171         Helper.assertEqualsToLargeString(hintsOnFill[2]);
    172 
    173         // Select dataset.
    174         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
    175         mUiBot.selectDataset("YO");
    176         autofillExpecation.assertAutoFilled();
    177 
    178         mActivity.syncRunOnUiThread(() -> {
    179             mActivity.mInput.setText("ID");
    180             mActivity.mPassword.setText("PASS");
    181             mActivity.mCommit.performClick();
    182         });
    183         final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
    184 
    185         // Save it...
    186         mUiBot.saveForAutofill(saveUi, true);
    187 
    188         // ... and assert results
    189         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
    190         final ViewNode inputOnSave = findNodeByResourceId(saveRequest.structure, ID_INPUT);
    191         assertTextAndValue(inputOnSave, "ID");
    192         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
    193 
    194         final String[] hintsOnSave = inputOnSave.getAutofillHints();
    195         // Cannot compare these large strings directly becauise it could cause ANR
    196         assertThat(hintsOnSave).hasLength(3);
    197         Helper.assertEqualsToLargeString(hintsOnSave[0]);
    198         Helper.assertEqualsToLargeString(hintsOnSave[1]);
    199         Helper.assertEqualsToLargeString(hintsOnSave[2]);
    200     }
    201 
    202     /**
    203      * Simple test that only uses UiAutomator to interact with the activity, so it indirectly
    204      * tests the integration of Autofill with Accessibility.
    205      */
    206     @Test
    207     public void testAutoFillOneDatasetAndSave_usingUiAutomatorOnly() throws Exception {
    208         startActivity();
    209 
    210         // Set service.
    211         enableService();
    212 
    213         // Set expectations.
    214         sReplier.addResponse(new CannedFillResponse.Builder()
    215                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
    216                 .addDataset(new CannedDataset.Builder()
    217                         .setField(ID_INPUT, "id")
    218                         .setField(ID_PASSWORD, "pass")
    219                         .setPresentation(createPresentation("YO"))
    220                         .build())
    221                 .build());
    222 
    223         // Trigger autofill.
    224         mUiBot.assertShownByRelativeId(ID_INPUT).click();
    225         sReplier.getNextFillRequest();
    226 
    227         // Select dataset...
    228         mUiBot.selectDataset("YO");
    229 
    230         // ...and assert autofilled values.
    231         final UiObject2 input = mUiBot.assertShownByRelativeId(ID_INPUT);
    232         final UiObject2 password = mUiBot.assertShownByRelativeId(ID_PASSWORD);
    233 
    234         assertWithMessage("wrong value for 'input'").that(input.getText()).isEqualTo("id");
    235         // TODO: password field is shown as **** ; ideally we should assert it's a password
    236         // field, but UiAutomator does not exposes that info.
    237         final String visiblePassword = password.getText();
    238         assertWithMessage("'password' should not be visible").that(visiblePassword)
    239             .isNotEqualTo("pass");
    240         assertWithMessage("wrong value for 'password'").that(visiblePassword).hasLength(4);
    241 
    242         // Trigger save...
    243         input.setText("ID");
    244         password.setText("PASS");
    245         mUiBot.assertShownByRelativeId(ID_COMMIT).click();
    246         mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
    247 
    248         // ... and assert results
    249         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
    250         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
    251         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
    252     }
    253 
    254     @Test
    255     @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
    256     public void testSave() throws Exception {
    257         saveTest(false);
    258     }
    259 
    260     @Test
    261     public void testSave_afterRotation() throws Exception {
    262         assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
    263         mUiBot.setScreenOrientation(UiBot.PORTRAIT);
    264         try {
    265             saveTest(true);
    266         } finally {
    267             try {
    268                 mUiBot.setScreenOrientation(UiBot.PORTRAIT);
    269                 cleanUpAfterScreenOrientationIsBackToPortrait();
    270             } catch (Exception e) {
    271                 mSafeCleanerRule.add(e);
    272             }
    273         }
    274     }
    275 
    276     private void saveTest(boolean rotate) throws Exception {
    277         startActivity();
    278 
    279         // Set service.
    280         enableService();
    281 
    282         // Set expectations.
    283         sReplier.addResponse(new CannedFillResponse.Builder()
    284                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    285                 .build());
    286 
    287         // Trigger autofill.
    288         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    289         sReplier.getNextFillRequest();
    290 
    291         // Trigger save.
    292         mActivity.syncRunOnUiThread(() -> {
    293             mActivity.mInput.setText("108");
    294             mActivity.mCommit.performClick();
    295         });
    296         UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
    297 
    298         if (rotate) {
    299             // After the device rotates, the input field get focus and generate a new session.
    300             sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
    301 
    302             mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
    303             saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
    304         }
    305 
    306         // Save it...
    307         mUiBot.saveForAutofill(saveUi, true);
    308 
    309         // ... and assert results
    310         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
    311         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
    312     }
    313 
    314     /**
    315      * Emulates an app dyanmically adding the password field after username is typed.
    316      */
    317     @Test
    318     @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
    319     public void testPartitionedSave() throws Exception {
    320         startActivity();
    321 
    322         // Set service.
    323         enableService();
    324 
    325         // 1st request
    326 
    327         // Set expectations.
    328         sReplier.addResponse(new CannedFillResponse.Builder()
    329                 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_INPUT)
    330                 .build());
    331 
    332         // Trigger autofill.
    333         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    334         sReplier.getNextFillRequest();
    335 
    336         // Set 1st field but don't commit session
    337         mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
    338         mUiBot.assertSaveNotShowing();
    339 
    340         // 2nd request
    341 
    342         // Set expectations.
    343         sReplier.addResponse(new CannedFillResponse.Builder()
    344                 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
    345                         ID_INPUT, ID_PASSWORD)
    346                 .build());
    347 
    348         // Trigger autofill.
    349         mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
    350         sReplier.getNextFillRequest();
    351 
    352         // Trigger save.
    353         mActivity.syncRunOnUiThread(() -> {
    354             mActivity.mPassword.setText("42");
    355             mActivity.mCommit.performClick();
    356         });
    357         final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME,
    358                 SAVE_DATA_TYPE_PASSWORD);
    359 
    360         // Save it...
    361         mUiBot.saveForAutofill(saveUi, true);
    362 
    363         // ... and assert results
    364         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
    365         assertThat(saveRequest.contexts.size()).isEqualTo(2);
    366 
    367         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
    368         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
    369     }
    370 
    371     /**
    372      * Emulates an app using fragments to display username and password in 2 steps.
    373      */
    374     @Test
    375     @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
    376     public void testDelayedSave() throws Exception {
    377         startActivity();
    378 
    379         // Set service.
    380         enableService();
    381 
    382         // 1st fragment.
    383 
    384         // Set expectations.
    385         sReplier.addResponse(new CannedFillResponse.Builder()
    386                 .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE).build());
    387 
    388         // Trigger autofill.
    389         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    390         sReplier.getNextFillRequest();
    391 
    392         // Trigger delayed save.
    393         mActivity.syncRunOnUiThread(() -> {
    394             mActivity.mInput.setText("108");
    395             mActivity.mCommit.performClick();
    396         });
    397         mUiBot.assertSaveNotShowing();
    398 
    399         // 2nd fragment.
    400 
    401         // Set expectations.
    402         sReplier.addResponse(new CannedFillResponse.Builder()
    403                 // Must explicitly set visitor, otherwise setRequiredSavableIds() would get the
    404                 // id from the 1st context
    405                 .setVisitor((contexts, builder) -> {
    406                     final AutofillId passwordId =
    407                             findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD);
    408                     final AutofillId inputId =
    409                             findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
    410                     builder.setSaveInfo(new SaveInfo.Builder(
    411                             SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
    412                             new AutofillId[] {inputId, passwordId})
    413                             .build());
    414                 })
    415                 .build());
    416 
    417         // Trigger autofill on second "fragment"
    418         mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
    419         sReplier.getNextFillRequest();
    420 
    421         // Trigger delayed save.
    422         mActivity.syncRunOnUiThread(() -> {
    423             mActivity.mPassword.setText("42");
    424             mActivity.mCommit.performClick();
    425         });
    426 
    427         // Save it...
    428         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
    429 
    430         // ... and assert results
    431         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
    432         assertThat(saveRequest.contexts.size()).isEqualTo(2);
    433 
    434         // Get username from 1st request.
    435         final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
    436         assertTextAndValue(findNodeByResourceId(structure1, ID_INPUT), "108");
    437 
    438         // Get password from 2nd request.
    439         final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
    440         assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "108");
    441         assertTextAndValue(findNodeByResourceId(structure2, ID_PASSWORD), "42");
    442     }
    443 
    444     @Test
    445     public void testSave_launchIntent() throws Exception {
    446         startActivity();
    447 
    448         // Set service.
    449         enableService();
    450 
    451         // Set expectations.
    452         sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell"))
    453                 .addResponse(new CannedFillResponse.Builder()
    454                         .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    455                         .build());
    456 
    457         // Trigger autofill.
    458         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    459         sReplier.getNextFillRequest();
    460 
    461         // Trigger save.
    462         mActivity.syncRunOnUiThread(() -> {
    463             mActivity.mInput.setText("108");
    464             mActivity.mCommit.performClick();
    465 
    466             // Disable autofill so it's not triggered again after WelcomeActivity finishes
    467             // and mActivity is resumed (with focus on mInput) after the session is closed
    468             mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
    469         });
    470 
    471         // Save it...
    472         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
    473         sReplier.getNextSaveRequest();
    474 
    475         // ... and assert activity was launched
    476         WelcomeActivity.assertShowing(mUiBot, "Saved by the bell");
    477     }
    478 
    479     @Test
    480     public void testSaveThenStartNewSessionRightAway() throws Exception {
    481         startActivity();
    482 
    483         // Set service.
    484         enableService();
    485 
    486         // Set expectations.
    487         sReplier.addResponse(new CannedFillResponse.Builder()
    488                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    489                 .build());
    490 
    491         // Trigger autofill.
    492         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    493         sReplier.getNextFillRequest();
    494 
    495         // Trigger save.
    496         mActivity.syncRunOnUiThread(() -> {
    497             mActivity.mInput.setText("108");
    498             mActivity.mCommit.performClick();
    499         });
    500 
    501         // Make sure Save UI for 1st session was shown....
    502         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
    503 
    504         // ...then start the new session right away (without finishing the activity).
    505         sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
    506         mActivity.syncRunOnUiThread(
    507                 () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput));
    508         sReplier.getNextFillRequest();
    509 
    510         // Make sure Save UI for 1st session was canceled.
    511         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    512     }
    513 
    514     @Test
    515     public void testSaveWithParcelableOnClientState() throws Exception {
    516         startActivity();
    517 
    518         // Set service.
    519         enableService();
    520 
    521         // Set expectations.
    522         final AutofillId id = new AutofillId(42);
    523         final Bundle clientState = new Bundle();
    524         clientState.putParcelable("id", id);
    525         clientState.putParcelable("my_id", new MyAutofillId(id));
    526         sReplier.addResponse(new CannedFillResponse.Builder()
    527                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    528                 .setExtras(clientState)
    529                 .build());
    530 
    531         // Trigger autofill.
    532         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    533         sReplier.getNextFillRequest();
    534 
    535         // Trigger save.
    536         mActivity.syncRunOnUiThread(() -> {
    537             mActivity.mInput.setText("108");
    538             mActivity.mCommit.performClick();
    539         });
    540         UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
    541 
    542         // Save it...
    543         mUiBot.saveForAutofill(saveUi, true);
    544 
    545         // ... and assert results
    546         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
    547         assertMyClientState(saveRequest.data);
    548 
    549         // Also check fillevent history
    550         final FillEventHistory history = InstrumentedAutoFillService.getFillEventHistory(1);
    551         @SuppressWarnings("deprecation")
    552         final Bundle deprecatedState = history.getClientState();
    553         assertMyClientState(deprecatedState);
    554         assertMyClientState(history.getEvents().get(0).getClientState());
    555     }
    556 
    557     private void assertMyClientState(Bundle data) {
    558         // Must set proper classpath before reading the data, otherwise Bundle will use it's
    559         // on class classloader, which is the framework's.
    560         data.setClassLoader(getClass().getClassLoader());
    561 
    562         final AutofillId expectedId = new AutofillId(42);
    563         final AutofillId actualId = data.getParcelable("id");
    564         assertThat(actualId).isEqualTo(expectedId);
    565         final MyAutofillId actualMyId = data.getParcelable("my_id");
    566         assertThat(actualMyId).isEqualTo(new MyAutofillId(expectedId));
    567     }
    568 
    569     @Test
    570     public void testCancelPreventsSaveUiFromShowing() throws Exception {
    571         startActivity();
    572 
    573         // Set service.
    574         enableService();
    575 
    576         // Set expectations.
    577         sReplier.addResponse(new CannedFillResponse.Builder()
    578                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    579                 .build());
    580 
    581         // Trigger autofill.
    582         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    583         sReplier.getNextFillRequest();
    584 
    585         // Cancel session.
    586         mActivity.getAutofillManager().cancel();
    587 
    588         // Trigger save.
    589         mActivity.syncRunOnUiThread(() -> {
    590             mActivity.mInput.setText("108");
    591             mActivity.mCommit.performClick();
    592         });
    593 
    594         // Assert it's not showing.
    595         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    596     }
    597 
    598     @Test
    599     public void testDismissSave_byTappingBack() throws Exception {
    600         startActivity();
    601         dismissSaveTest(DismissType.BACK_BUTTON);
    602     }
    603 
    604     @Test
    605     public void testDismissSave_byTappingHome() throws Exception {
    606         startActivity();
    607         dismissSaveTest(DismissType.HOME_BUTTON);
    608     }
    609 
    610     @Test
    611     public void testDismissSave_byTouchingOutside() throws Exception {
    612         startActivity();
    613         dismissSaveTest(DismissType.TOUCH_OUTSIDE);
    614     }
    615 
    616     @Test
    617     public void testDismissSave_byFocusingOutside() throws Exception {
    618         startActivity();
    619         dismissSaveTest(DismissType.FOCUS_OUTSIDE);
    620     }
    621 
    622     private void dismissSaveTest(DismissType dismissType) throws Exception {
    623         // Set service.
    624         enableService();
    625 
    626         // Set expectations.
    627         sReplier.addResponse(new CannedFillResponse.Builder()
    628                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    629                 .build());
    630 
    631         // Trigger autofill.
    632         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    633         sReplier.getNextFillRequest();
    634 
    635         // Trigger save.
    636         mActivity.syncRunOnUiThread(() -> {
    637             mActivity.mInput.setText("108");
    638             mActivity.mCommit.performClick();
    639         });
    640         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
    641 
    642         // Then make sure it goes away when user doesn't want it..
    643         switch (dismissType) {
    644             case BACK_BUTTON:
    645                 mUiBot.pressBack();
    646                 break;
    647             case HOME_BUTTON:
    648                 mUiBot.pressHome();
    649                 break;
    650             case TOUCH_OUTSIDE:
    651                 mUiBot.assertShownByText(TEXT_LABEL).click();
    652                 break;
    653             case FOCUS_OUTSIDE:
    654                 mActivity.syncRunOnUiThread(() -> mActivity.mLabel.requestFocus());
    655                 mUiBot.assertShownByText(TEXT_LABEL).click();
    656                 break;
    657             default:
    658                 throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
    659         }
    660         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    661     }
    662 
    663     @Test
    664     public void testTapHomeWhileDatasetPickerUiIsShowing() throws Exception {
    665         startActivity();
    666         enableService();
    667         final MyAutofillCallback callback = mActivity.registerCallback();
    668 
    669         // Set expectations.
    670         sReplier.addResponse(new CannedFillResponse.Builder()
    671                 .addDataset(new CannedDataset.Builder()
    672                         .setField(ID_INPUT, "id")
    673                         .setField(ID_PASSWORD, "pass")
    674                         .setPresentation(createPresentation("YO"))
    675                         .build())
    676                 .build());
    677 
    678         // Trigger autofill.
    679         mUiBot.assertShownByRelativeId(ID_INPUT).click();
    680         sReplier.getNextFillRequest();
    681         mUiBot.assertDatasets("YO");
    682         callback.assertUiShownEvent(mActivity.mInput);
    683 
    684         // Go home, you are drunk!
    685         mUiBot.pressHome();
    686         mUiBot.assertNoDatasets();
    687         callback.assertUiHiddenEvent(mActivity.mInput);
    688 
    689         // Switch back to the activity.
    690         restartActivity();
    691         mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
    692         final UiObject2 datasetPicker = mUiBot.assertDatasets("YO");
    693         callback.assertUiShownEvent(mActivity.mInput);
    694 
    695         // Now autofill it.
    696         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
    697         mUiBot.selectDataset(datasetPicker, "YO");
    698         autofillExpecation.assertAutoFilled();
    699     }
    700 
    701     @Test
    702     public void testTapHomeWhileSaveUiIsShowing() throws Exception {
    703         startActivity();
    704         enableService();
    705 
    706         // Set expectations.
    707         sReplier.addResponse(new CannedFillResponse.Builder()
    708                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    709                 .build());
    710 
    711         // Trigger autofill.
    712         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    713         sReplier.getNextFillRequest();
    714         mUiBot.assertNoDatasetsEver();
    715 
    716         // Trigger save, but don't tap it.
    717         mActivity.syncRunOnUiThread(() -> {
    718             mActivity.mInput.setText("108");
    719             mActivity.mCommit.performClick();
    720         });
    721         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
    722 
    723         // Go home, you are drunk!
    724         mUiBot.pressHome();
    725         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    726 
    727         // Prepare the response for the next session, which will be automatically triggered
    728         // when the activity is brought back.
    729         sReplier.addResponse(new CannedFillResponse.Builder()
    730                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
    731                 .addDataset(new CannedDataset.Builder()
    732                         .setField(ID_INPUT, "id")
    733                         .setField(ID_PASSWORD, "pass")
    734                         .setPresentation(createPresentation("YO"))
    735                         .build())
    736                 .build());
    737 
    738         // Switch back to the activity.
    739         restartActivity();
    740         mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
    741         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    742         sReplier.getNextFillRequest();
    743         mUiBot.assertNoDatasetsEver();
    744 
    745         // Trigger and select UI.
    746         mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
    747         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
    748         mUiBot.selectDataset("YO");
    749 
    750         // Assert it.
    751         autofillExpecation.assertAutoFilled();
    752     }
    753 
    754     @Override
    755     protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
    756             throws Exception {
    757         startActivity();
    758         // Set service.
    759         enableService();
    760 
    761         // Set expectations.
    762         sReplier.addResponse(new CannedFillResponse.Builder()
    763                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    764                 .setSaveInfoVisitor((contexts, builder) -> builder
    765                         .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
    766                 .build());
    767 
    768         // Trigger autofill.
    769         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    770         sReplier.getNextFillRequest();
    771 
    772         // Trigger save.
    773         mActivity.syncRunOnUiThread(() -> {
    774             mActivity.mInput.setText("108");
    775             mActivity.mCommit.performClick();
    776         });
    777         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
    778 
    779         // Tap the link.
    780         tapSaveUiLink(saveUi);
    781 
    782         // Make sure new activity is shown...
    783         WelcomeActivity.assertShowingDefaultMessage(mUiBot);
    784         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    785 
    786         // .. then do something to return to previous activity...
    787         switch (type) {
    788             case ROTATE_THEN_TAP_BACK_BUTTON:
    789                 // After the device rotates, the input field get focus and generate a new session.
    790                 sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
    791 
    792                 mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
    793                 WelcomeActivity.assertShowingDefaultMessage(mUiBot);
    794                 // not breaking on purpose
    795             case TAP_BACK_BUTTON:
    796                 // ..then go back and save it.
    797                 mUiBot.pressBack();
    798                 break;
    799             case FINISH_ACTIVITY:
    800                 // ..then finishes it.
    801                 WelcomeActivity.finishIt();
    802                 break;
    803             default:
    804                 throw new IllegalArgumentException("invalid type: " + type);
    805         }
    806         // Make sure previous activity is back...
    807         mUiBot.assertShownByRelativeId(ID_INPUT);
    808 
    809         // ... and tap save.
    810         final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
    811         mUiBot.saveForAutofill(newSaveUi, true);
    812 
    813         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
    814         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
    815 
    816     }
    817 
    818     @Override
    819     protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
    820         sReplier.getNextFillRequest();
    821     }
    822 
    823     @Override
    824     protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
    825             boolean manualRequest) throws Exception {
    826         startActivity();
    827         // Set service.
    828         enableService();
    829 
    830         // Set expectations.
    831         sReplier.addResponse(new CannedFillResponse.Builder()
    832                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    833                 .setSaveInfoVisitor((contexts, builder) -> builder
    834                         .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
    835                 .build());
    836 
    837         // Trigger autofill.
    838         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    839         sReplier.getNextFillRequest();
    840 
    841         // Trigger save.
    842         mActivity.syncRunOnUiThread(() -> {
    843             mActivity.mInput.setText("108");
    844             mActivity.mCommit.performClick();
    845         });
    846         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
    847 
    848         // Tap the link.
    849         tapSaveUiLink(saveUi);
    850 
    851         // Make sure new activity is shown.
    852         WelcomeActivity.assertShowingDefaultMessage(mUiBot);
    853         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    854 
    855         // Tap back to restore the Save UI...
    856         mUiBot.pressBack();
    857         // Make sure previous activity is back...
    858         mUiBot.assertShownByRelativeId(ID_LABEL);
    859 
    860         // ...but don't tap it...
    861         final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
    862 
    863         // ...instead, do something to dismiss it:
    864         switch (action) {
    865             case TOUCH_OUTSIDE:
    866                 mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
    867                 break;
    868             case TAP_NO_ON_SAVE_UI:
    869                 mUiBot.saveForAutofill(saveUi2, false);
    870                 break;
    871             case TAP_YES_ON_SAVE_UI:
    872                 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
    873                 final SaveRequest saveRequest = sReplier.getNextSaveRequest();
    874                 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
    875                 break;
    876             default:
    877                 throw new IllegalArgumentException("invalid action: " + action);
    878         }
    879         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    880 
    881         // Now triggers a new session and do business as usual...
    882         sReplier.addResponse(new CannedFillResponse.Builder()
    883                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    884                 .build());
    885 
    886         // Trigger autofill.
    887         if (manualRequest) {
    888             mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
    889         } else {
    890             mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
    891         }
    892 
    893         sReplier.getNextFillRequest();
    894 
    895         // Trigger save.
    896         mActivity.syncRunOnUiThread(() -> {
    897             mActivity.mInput.setText("42");
    898             mActivity.mCommit.performClick();
    899         });
    900 
    901         // Save it...
    902         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
    903 
    904         // ... and assert results
    905         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
    906         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
    907     }
    908 
    909     @Override
    910     protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
    911             throws Exception {
    912         startActivity(false);
    913         // Set service.
    914         enableService();
    915 
    916         // Set expectations.
    917         sReplier.addResponse(new CannedFillResponse.Builder()
    918                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
    919                 .setSaveInfoVisitor((contexts, builder) -> builder
    920                         .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
    921                 .build());
    922 
    923         // Trigger autofill.
    924         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    925         sReplier.getNextFillRequest();
    926 
    927         // Trigger save.
    928         mActivity.syncRunOnUiThread(() -> {
    929             mActivity.mInput.setText("108");
    930             mActivity.mCommit.performClick();
    931         });
    932         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
    933 
    934         // Tap the link.
    935         tapSaveUiLink(saveUi);
    936         // Make sure new activity is shown...
    937         WelcomeActivity.assertShowingDefaultMessage(mUiBot);
    938         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    939 
    940         switch (type) {
    941             case LAUNCH_PREVIOUS_ACTIVITY:
    942                 startActivityOnNewTask(SimpleSaveActivity.class);
    943                 break;
    944             case LAUNCH_NEW_ACTIVITY:
    945                 // Launch a 3rd activity...
    946                 startActivityOnNewTask(LoginActivity.class);
    947                 mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
    948                 // ...then go back
    949                 mUiBot.pressBack();
    950                 break;
    951             default:
    952                 throw new IllegalArgumentException("invalid type: " + type);
    953         }
    954         // Make sure right activity is showing
    955         mUiBot.assertShownByRelativeId(ID_INPUT, Timeouts.ACTIVITY_RESURRECTION);
    956 
    957         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    958     }
    959 
    960     @Test
    961     @AppModeFull(reason = "Service-specific test")
    962     public void testSelectedDatasetsAreSentOnSaveRequest() throws Exception {
    963         startActivity();
    964 
    965         // Set service.
    966         enableService();
    967 
    968         // Set expectations.
    969         sReplier.addResponse(new CannedFillResponse.Builder()
    970                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
    971                 // Added on reversed order on purpose
    972                 .addDataset(new CannedDataset.Builder()
    973                         .setId("D2")
    974                         .setField(ID_INPUT, "id again")
    975                         .setField(ID_PASSWORD, "pass")
    976                         .setPresentation(createPresentation("D2"))
    977                         .build())
    978                 .addDataset(new CannedDataset.Builder()
    979                         .setId("D1")
    980                         .setField(ID_INPUT, "id")
    981                         .setPresentation(createPresentation("D1"))
    982                         .build())
    983                 .build());
    984 
    985         // Trigger autofill.
    986         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
    987         sReplier.getNextFillRequest();
    988 
    989         // Select 1st dataset.
    990         final FillExpectation autofillExpecation1 = mActivity.expectAutoFill("id");
    991         final UiObject2 picker1 = mUiBot.assertDatasets("D2", "D1");
    992         mUiBot.selectDataset(picker1, "D1");
    993         autofillExpecation1.assertAutoFilled();
    994 
    995         // Select 2nd dataset.
    996         mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
    997         final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass");
    998         final UiObject2 picker2 = mUiBot.assertDatasets("D2");
    999         mUiBot.selectDataset(picker2, "D2");
   1000         autofillExpecation2.assertAutoFilled();
   1001 
   1002         mActivity.syncRunOnUiThread(() -> {
   1003             mActivity.mInput.setText("ID");
   1004             mActivity.mPassword.setText("PASS");
   1005             mActivity.mCommit.performClick();
   1006         });
   1007         final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
   1008 
   1009         // Save it...
   1010         mUiBot.saveForAutofill(saveUi, true);
   1011 
   1012         // ... and assert results
   1013         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
   1014         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
   1015         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
   1016         assertThat(saveRequest.datasetIds).containsExactly("D1", "D2").inOrder();
   1017     }
   1018 
   1019     @Override
   1020     protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
   1021             throws Exception {
   1022         // Prepare activity.
   1023         startActivity();
   1024         mActivity.mInput.getRootView()
   1025                 .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
   1026 
   1027         // Set service.
   1028         enableService();
   1029 
   1030         // Set expectations.
   1031         sReplier.addResponse(new CannedFillResponse.Builder()
   1032                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
   1033                 .setSaveInfoVisitor((contexts, builder) -> builder
   1034                         .setCustomDescription(
   1035                                 newCustomDescription(TrampolineWelcomeActivity.class)))
   1036                 .build());
   1037 
   1038         // Trigger autofill.
   1039         mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
   1040         sReplier.getNextFillRequest();
   1041 
   1042         // Trigger save.
   1043         mActivity.syncRunOnUiThread(() -> {
   1044             mActivity.mInput.setText("108");
   1045             mActivity.mCommit.performClick();
   1046         });
   1047         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
   1048 
   1049         // Tap the link.
   1050         tapSaveUiLink(saveUi);
   1051 
   1052         // Make sure new activity is shown...
   1053         WelcomeActivity.assertShowingDefaultMessage(mUiBot);
   1054 
   1055         // Save UI should be showing as well, since Trampoline finished.
   1056         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
   1057 
   1058         // Dismiss Save Dialog
   1059         mUiBot.pressBack();
   1060         // Go back and make sure it's showing the right activity.
   1061         mUiBot.pressBack();
   1062         mUiBot.assertShownByRelativeId(ID_LABEL);
   1063 
   1064         // Now start a new session.
   1065         sReplier.addResponse(new CannedFillResponse.Builder()
   1066                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
   1067                 .build());
   1068         mActivity.getAutofillManager().requestAutofill(mActivity.mPassword);
   1069         sReplier.getNextFillRequest();
   1070         mActivity.syncRunOnUiThread(() -> {
   1071             mActivity.mPassword.setText("42");
   1072             mActivity.mCommit.performClick();
   1073         });
   1074         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
   1075         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
   1076         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
   1077         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
   1078     }
   1079 
   1080     @Test
   1081     public void testSanitizeOnSaveWhenAppChangeValues() throws Exception {
   1082         startActivity();
   1083 
   1084         // Set listeners that will change the saved value
   1085         new AntiTrimmerTextWatcher(mActivity.mInput);
   1086         new AntiTrimmerTextWatcher(mActivity.mPassword);
   1087 
   1088         // Set service.
   1089         enableService();
   1090 
   1091         // Set expectations.
   1092         sReplier.addResponse(new CannedFillResponse.Builder()
   1093                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
   1094                 .setSaveInfoVisitor((contexts, builder) -> {
   1095                     final FillContext context = contexts.get(0);
   1096                     final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
   1097                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
   1098                     builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
   1099                             passwordId);
   1100                 })
   1101                 .build());
   1102 
   1103         // Trigger autofill.
   1104         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
   1105         sReplier.getNextFillRequest();
   1106 
   1107         // Trigger save.
   1108         mActivity.syncRunOnUiThread(() -> {
   1109             mActivity.mInput.setText("id");
   1110             mActivity.mPassword.setText("pass");
   1111             mActivity.mCommit.performClick();
   1112         });
   1113 
   1114         // Save it...
   1115         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
   1116 
   1117         // ... and assert results
   1118         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
   1119         assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
   1120         assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
   1121     }
   1122 
   1123     @Test
   1124     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
   1125     public void testSanitizeOnSaveNoChange() throws Exception {
   1126         startActivity();
   1127 
   1128         // Set service.
   1129         enableService();
   1130 
   1131         // Set expectations.
   1132         sReplier.addResponse(new CannedFillResponse.Builder()
   1133                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
   1134                 .setOptionalSavableIds(ID_PASSWORD)
   1135                 .setSaveInfoVisitor((contexts, builder) -> {
   1136                     final FillContext context = contexts.get(0);
   1137                     final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
   1138                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
   1139                     builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
   1140                             passwordId);
   1141                 })
   1142                 .build());
   1143 
   1144         // Trigger autofill.
   1145         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
   1146         sReplier.getNextFillRequest();
   1147         mUiBot.assertNoDatasetsEver();
   1148 
   1149         // Trigger save.
   1150         mActivity.syncRunOnUiThread(() -> {
   1151             mActivity.mInput.setText("#id#");
   1152             mActivity.mPassword.setText("#pass#");
   1153             mActivity.mCommit.performClick();
   1154         });
   1155 
   1156         // Save it...
   1157         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
   1158 
   1159         // ... and assert results
   1160         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
   1161         assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
   1162         assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
   1163     }
   1164 
   1165     @Test
   1166     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
   1167     public void testDontSaveWhenSanitizedValueForRequiredFieldDidntChange() throws Exception {
   1168         startActivity();
   1169 
   1170         // Set listeners that will change the saved value
   1171         new AntiTrimmerTextWatcher(mActivity.mInput);
   1172         new AntiTrimmerTextWatcher(mActivity.mPassword);
   1173 
   1174         // Set service.
   1175         enableService();
   1176 
   1177         // Set expectations.
   1178         sReplier.addResponse(new CannedFillResponse.Builder()
   1179                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
   1180                 .setSaveInfoVisitor((contexts, builder) -> {
   1181                     final FillContext context = contexts.get(0);
   1182                     final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
   1183                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
   1184                     builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
   1185                             passwordId);
   1186                 })
   1187                 .addDataset(new CannedDataset.Builder()
   1188                         .setField(ID_INPUT, "id")
   1189                         .setField(ID_PASSWORD, "pass")
   1190                         .setPresentation(createPresentation("YO"))
   1191                         .build())
   1192                 .build());
   1193 
   1194         // Trigger autofill.
   1195         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
   1196         sReplier.getNextFillRequest();
   1197 
   1198         mActivity.syncRunOnUiThread(() -> {
   1199             mActivity.mInput.setText("id");
   1200             mActivity.mPassword.setText("pass");
   1201             mActivity.mCommit.performClick();
   1202         });
   1203 
   1204         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
   1205     }
   1206 
   1207     @Test
   1208     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
   1209     public void testDontSaveWhenSanitizedValueForOptionalFieldDidntChange() throws Exception {
   1210         startActivity();
   1211 
   1212         // Set service.
   1213         enableService();
   1214 
   1215         // Set expectations.
   1216         sReplier.addResponse(new CannedFillResponse.Builder()
   1217                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
   1218                 .setOptionalSavableIds(ID_PASSWORD)
   1219                 .setSaveInfoVisitor((contexts, builder) -> {
   1220                     final FillContext context = contexts.get(0);
   1221                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
   1222                     builder.addSanitizer(new TextValueSanitizer(Pattern.compile("(pass) "), "$1"),
   1223                             passwordId);
   1224                 })
   1225                 .addDataset(new CannedDataset.Builder()
   1226                         .setField(ID_INPUT, "id")
   1227                         .setField(ID_PASSWORD, "pass")
   1228                         .setPresentation(createPresentation("YO"))
   1229                         .build())
   1230                 .build());
   1231 
   1232         // Trigger autofill.
   1233         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
   1234         sReplier.getNextFillRequest();
   1235 
   1236         mActivity.syncRunOnUiThread(() -> {
   1237             mActivity.mInput.setText("id");
   1238             mActivity.mPassword.setText("#pass#");
   1239             mActivity.mCommit.performClick();
   1240         });
   1241 
   1242         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
   1243     }
   1244 
   1245     @Test
   1246     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
   1247     public void testDontSaveWhenRequiredFieldFailedSanitization() throws Exception {
   1248         startActivity();
   1249 
   1250         // Set service.
   1251         enableService();
   1252 
   1253         // Set expectations.
   1254         sReplier.addResponse(new CannedFillResponse.Builder()
   1255                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
   1256                 .setSaveInfoVisitor((contexts, builder) -> {
   1257                     final FillContext context = contexts.get(0);
   1258                     final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
   1259                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
   1260                     builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
   1261                             inputId, passwordId);
   1262                 })
   1263                 .addDataset(new CannedDataset.Builder()
   1264                         .setField(ID_INPUT, "#id#")
   1265                         .setField(ID_PASSWORD, "#pass#")
   1266                         .setPresentation(createPresentation("YO"))
   1267                         .build())
   1268                 .build());
   1269 
   1270         // Trigger autofill.
   1271         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
   1272         sReplier.getNextFillRequest();
   1273 
   1274         mActivity.syncRunOnUiThread(() -> {
   1275             mActivity.mInput.setText("id");
   1276             mActivity.mPassword.setText("pass");
   1277             mActivity.mCommit.performClick();
   1278         });
   1279 
   1280         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
   1281     }
   1282 
   1283     @Test
   1284     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
   1285     public void testDontSaveWhenOptionalFieldFailedSanitization() throws Exception {
   1286         startActivity();
   1287 
   1288         // Set service.
   1289         enableService();
   1290 
   1291         // Set expectations.
   1292         sReplier.addResponse(new CannedFillResponse.Builder()
   1293                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
   1294                 .setOptionalSavableIds(ID_PASSWORD)
   1295                 .setSaveInfoVisitor((contexts, builder) -> {
   1296                     final FillContext context = contexts.get(0);
   1297                     final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
   1298                     final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
   1299                     builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
   1300                             inputId, passwordId);
   1301 
   1302                 })
   1303                 .addDataset(new CannedDataset.Builder()
   1304                         .setField(ID_INPUT, "id")
   1305                         .setField(ID_PASSWORD, "#pass#")
   1306                         .setPresentation(createPresentation("YO"))
   1307                         .build())
   1308                 .build());
   1309 
   1310         // Trigger autofill.
   1311         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
   1312         sReplier.getNextFillRequest();
   1313 
   1314         mActivity.syncRunOnUiThread(() -> {
   1315             mActivity.mInput.setText("id");
   1316             mActivity.mPassword.setText("pass");
   1317             mActivity.mCommit.performClick();
   1318         });
   1319 
   1320         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
   1321     }
   1322 
   1323     @Test
   1324     @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
   1325     public void testDontSaveWhenInitialValueAndNoUserInputAndServiceDatasets() throws Throwable {
   1326         // Prepare activitiy.
   1327         startActivity();
   1328         mActivity.syncRunOnUiThread(() -> {
   1329             // NOTE: input's value must be a subset of the dataset value, otherwise the dataset
   1330             // picker is filtered out
   1331             mActivity.mInput.setText("f");
   1332             mActivity.mPassword.setText("b");
   1333         });
   1334 
   1335         // Set service.
   1336         enableService();
   1337 
   1338         // Set expectations.
   1339         sReplier.addResponse(new CannedFillResponse.Builder()
   1340                 .addDataset(new CannedDataset.Builder()
   1341                         .setField(ID_INPUT, "foo")
   1342                         .setField(ID_PASSWORD, "bar")
   1343                         .setPresentation(createPresentation("The Dude"))
   1344                         .build())
   1345                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT, ID_PASSWORD).build());
   1346 
   1347         // Trigger auto-fill.
   1348         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
   1349         sReplier.getNextFillRequest();
   1350         mUiBot.assertDatasets("The Dude");
   1351 
   1352         // Trigger save.
   1353         mActivity.getAutofillManager().commit();
   1354 
   1355         // Assert it's not showing.
   1356         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
   1357     }
   1358 
   1359     @Test
   1360     public void testExplicitySaveButton() throws Exception {
   1361         explicitySaveButtonTest(false, 0);
   1362     }
   1363 
   1364     @Test
   1365     public void testExplicitySaveButtonWhenAppClearFields() throws Exception {
   1366         explicitySaveButtonTest(true, 0);
   1367     }
   1368 
   1369     @Test
   1370     public void testExplicitySaveButtonOnly() throws Exception {
   1371         explicitySaveButtonTest(false, SaveInfo.FLAG_DONT_SAVE_ON_FINISH);
   1372     }
   1373 
   1374     /**
   1375      * Tests scenario where service explicitly indicates which button is used to save.
   1376      */
   1377     private void explicitySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception {
   1378         final boolean testBitmap = false;
   1379         startActivity();
   1380         mActivity.setAutoCommit(false);
   1381         mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit);
   1382 
   1383         // Set service.
   1384         enableService();
   1385 
   1386         // Set expectations.
   1387         sReplier.addResponse(new CannedFillResponse.Builder()
   1388                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
   1389                 .setSaveTriggerId(mActivity.mCommit.getAutofillId())
   1390                 .setSaveInfoFlags(flags)
   1391                 .build());
   1392 
   1393         // Trigger autofill.
   1394         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
   1395         sReplier.getNextFillRequest();
   1396 
   1397         // Trigger save.
   1398         mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
   1399 
   1400         // Take a screenshot to make sure button doesn't disappear.
   1401         final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
   1402         assertThat(commitBefore.toUpperCase()).isEqualTo("COMMIT");
   1403         // Disable unnecessary screenshot tests as takeScreenshot() fails on some device.
   1404 
   1405         final Bitmap screenshotBefore = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
   1406                 : null;
   1407 
   1408         // Save it...
   1409         mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
   1410         final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
   1411         mUiBot.saveForAutofill(saveUi, true);
   1412 
   1413         // Make sure save button is showning (it was removed on earlier versions of the feature)
   1414         final String commitAfter = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
   1415         assertThat(commitAfter.toUpperCase()).isEqualTo("COMMIT");
   1416         final Bitmap screenshotAfter = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
   1417                 : null;
   1418 
   1419         // ... and assert results
   1420         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
   1421         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
   1422 
   1423         if (testBitmap) {
   1424             Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter);
   1425         }
   1426     }
   1427 
   1428     @Override
   1429     protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
   1430         startActivity();
   1431         // Set service.
   1432         enableService();
   1433 
   1434         // Set expectations.
   1435         sReplier.addResponse(new CannedFillResponse.Builder()
   1436                 .setSaveInfoVisitor((contexts, builder) -> {
   1437                     // Set response with custom description
   1438                     final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
   1439                     final CustomDescription.Builder customDescription =
   1440                             newCustomDescriptionBuilder(WelcomeActivity.class);
   1441                     final RemoteViews update = newTemplate();
   1442                     if (updateLinkView) {
   1443                         update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
   1444                     } else {
   1445                         update.setCharSequence(R.id.static_text, "setText", "ME!");
   1446                     }
   1447                     Validator validCondition = new RegexValidator(id, Pattern.compile(".*"));
   1448                     customDescription.batchUpdate(validCondition,
   1449                             new BatchUpdates.Builder().updateTemplate(update).build());
   1450 
   1451                     builder.setCustomDescription(customDescription.build());
   1452                 })
   1453                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
   1454                 .build());
   1455 
   1456         // Trigger autofill.
   1457         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
   1458         sReplier.getNextFillRequest();
   1459         // Trigger save.
   1460         mActivity.syncRunOnUiThread(() -> {
   1461             mActivity.mInput.setText("108");
   1462             mActivity.mCommit.performClick();
   1463         });
   1464         final UiObject2 saveUi;
   1465         if (updateLinkView) {
   1466             saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC, "TAP ME IF YOU CAN");
   1467         } else {
   1468             saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
   1469             final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
   1470             assertThat(changed.getText()).isEqualTo("ME!");
   1471         }
   1472 
   1473         // Tap the link.
   1474         tapSaveUiLink(saveUi);
   1475 
   1476         // Make sure new activity is shown...
   1477         WelcomeActivity.assertShowingDefaultMessage(mUiBot);
   1478         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
   1479     }
   1480 }
   1481