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 
     17 package android.autofillservice.cts;
     18 
     19 import static android.autofillservice.cts.FragmentContainerActivity.FRAGMENT_TAG;
     20 import static android.autofillservice.cts.Helper.findNodeByResourceId;
     21 import static android.autofillservice.cts.Helper.getContext;
     22 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
     23 
     24 import static com.google.common.truth.Truth.assertThat;
     25 
     26 import android.app.Fragment;
     27 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
     28 import android.content.Intent;
     29 import android.service.autofill.SaveInfo;
     30 import androidx.annotation.NonNull;
     31 import androidx.annotation.Nullable;
     32 import android.view.ViewGroup;
     33 import android.widget.EditText;
     34 
     35 import org.junit.Before;
     36 import org.junit.Rule;
     37 import org.junit.Test;
     38 
     39 import java.util.concurrent.atomic.AtomicReference;
     40 
     41 /**
     42  * Tests that the session finishes when the views and fragments go away
     43  */
     44 public class AutoFinishSessionTest extends AutoFillServiceTestCase {
     45 
     46     private static final String ID_BUTTON = "button";
     47 
     48     @Rule
     49     public final AutofillActivityTestRule<FragmentContainerActivity> mActivityRule =
     50             new AutofillActivityTestRule<>(FragmentContainerActivity.class);
     51     private FragmentContainerActivity mActivity;
     52     private EditText mEditText1;
     53     private EditText mEditText2;
     54     private Fragment mFragment;
     55     private ViewGroup mParent;
     56 
     57     @Before
     58     public void initViews() {
     59         mActivity = mActivityRule.getActivity();
     60         mEditText1 = mActivity.findViewById(R.id.editText1);
     61         mEditText2 = mActivity.findViewById(R.id.editText2);
     62         mFragment = mActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
     63         mParent = ((ViewGroup) mEditText1.getParent());
     64 
     65         assertThat(mFragment).isNotNull();
     66     }
     67 
     68     // firstRemove and secondRemove run in the UI Thread; firstCheck doesn't
     69     private void removeViewsBaseTest(@NonNull Runnable firstRemove, @Nullable Runnable firstCheck,
     70             @Nullable Runnable secondRemove, String... viewsToSave) throws Exception {
     71         enableService();
     72 
     73         // Set expectations.
     74         sReplier.addResponse(new CannedFillResponse.Builder()
     75                 .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
     76                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, viewsToSave).build());
     77 
     78         // Trigger autofill
     79         mActivity.syncRunOnUiThread(() -> {
     80             mEditText2.requestFocus();
     81             mEditText1.requestFocus();
     82         });
     83 
     84         sReplier.getNextFillRequest();
     85 
     86         mUiBot.assertNoDatasetsEver();
     87 
     88         // remove first set of views
     89         mActivity.syncRunOnUiThread(() -> {
     90             mEditText1.setText("editText1-filled");
     91             mEditText2.setText("editText2-filled");
     92             firstRemove.run();
     93         });
     94 
     95         // Check state between remove operations
     96         if (firstCheck != null) {
     97             firstCheck.run();
     98         }
     99 
    100         // remove second set of views
    101         if (secondRemove != null) {
    102             mActivity.syncRunOnUiThread(secondRemove);
    103         }
    104 
    105         // Save should be shows after all remove operations were executed
    106         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
    107 
    108         SaveRequest saveRequest = sReplier.getNextSaveRequest();
    109         for (String view : viewsToSave) {
    110             assertThat(findNodeByResourceId(saveRequest.structure, view)
    111                     .getAutofillValue().getTextValue().toString()).isEqualTo(view + "-filled");
    112         }
    113     }
    114 
    115     @Test
    116     public void removeBothViewsToFinishSession() throws Exception {
    117         final AtomicReference<Exception> ref = new AtomicReference<>();
    118         removeViewsBaseTest(
    119                 () -> ((ViewGroup) mEditText1.getParent()).removeView(mEditText1),
    120                 () -> assertSaveNotShowing(ref),
    121                 () -> ((ViewGroup) mEditText2.getParent()).removeView(mEditText2),
    122                 "editText1", "editText2");
    123         final Exception e = ref.get();
    124         if (e != null) {
    125             throw e;
    126         }
    127     }
    128 
    129     private void assertSaveNotShowing(AtomicReference<Exception> ref) {
    130         try {
    131             mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    132         } catch (Exception e) {
    133             ref.set(e);
    134         }
    135     }
    136 
    137     @Test
    138     public void removeOneViewToFinishSession() throws Exception {
    139         removeViewsBaseTest(
    140                 () -> {
    141                     // Do not trigger new partition when switching to editText2
    142                     mEditText2.setFocusable(false);
    143 
    144                     mParent.removeView(mEditText1);
    145                 },
    146                 null,
    147                 null,
    148                 "editText1");
    149     }
    150 
    151     @Test
    152     public void hideOneViewToFinishSession() throws Exception {
    153         removeViewsBaseTest(
    154                 () -> {
    155                     // Do not trigger new partition when switching to editText2
    156                     mEditText2.setFocusable(false);
    157 
    158                     mEditText1.setVisibility(ViewGroup.INVISIBLE);
    159                 },
    160                 null,
    161                 null,
    162                 "editText1");
    163     }
    164 
    165     @Test
    166     public void removeFragmentToFinishSession() throws Exception {
    167         removeViewsBaseTest(
    168                 () -> mActivity.getFragmentManager().beginTransaction().remove(
    169                         mFragment).commitNow(),
    170                 null,
    171                 null,
    172                 "editText1", "editText2");
    173     }
    174 
    175     @Test
    176     public void removeParentToFinishSession() throws Exception {
    177         removeViewsBaseTest(
    178                 () -> ((ViewGroup) mParent.getParent()).removeView(mParent),
    179                 null,
    180                 null,
    181                 "editText1", "editText2");
    182     }
    183 
    184     @Test
    185     public void hideParentToFinishSession() throws Exception {
    186         removeViewsBaseTest(
    187                 () -> mParent.setVisibility(ViewGroup.INVISIBLE),
    188                 null,
    189                 null,
    190                 "editText1", "editText2");
    191     }
    192 
    193     /**
    194      * An activity that is currently getting autofilled might go into the background. While the
    195      * tracked views are not visible on the screen anymore, this should not trigger a save.
    196      *
    197      * <p>The {@link Runnable}s are synchronously run in the UI thread.
    198      */
    199     private void activityToBackgroundShouldNotTriggerSave(@Nullable Runnable removeInBackGround,
    200             @Nullable Runnable removeInForeGroup) throws Exception {
    201         enableService();
    202 
    203         // Set expectations.
    204         sReplier.addResponse(new CannedFillResponse.Builder()
    205                 .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
    206                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText1").build());
    207 
    208         // Trigger autofill
    209         mActivity.syncRunOnUiThread(() -> {
    210             mEditText2.requestFocus();
    211             mEditText1.requestFocus();
    212         });
    213 
    214         sReplier.getNextFillRequest();
    215 
    216         mUiBot.assertNoDatasetsEver();
    217 
    218         mActivity.syncRunOnUiThread(() -> {
    219             mEditText1.setText("editText1-filled");
    220             mEditText2.setText("editText2-filled");
    221         });
    222 
    223         // Start activity on top
    224         mActivity.startActivity(new Intent(getContext(),
    225                 ManualAuthenticationActivity.class));
    226         mActivity.waitUntilStopped();
    227 
    228         if (removeInBackGround != null) {
    229             mActivity.syncRunOnUiThread(removeInBackGround);
    230         }
    231 
    232         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    233 
    234         // Remove previously started activity from top
    235         mUiBot.selectByRelativeId(ID_BUTTON);
    236         mActivity.waitUntilResumed();
    237 
    238         if (removeInForeGroup != null) {
    239             mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
    240 
    241             mActivity.syncRunOnUiThread(removeInForeGroup);
    242         }
    243 
    244         // Save should be shows after all remove operations were executed
    245         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
    246 
    247         SaveRequest saveRequest = sReplier.getNextSaveRequest();
    248         assertThat(findNodeByResourceId(saveRequest.structure, "editText1")
    249                 .getAutofillValue().getTextValue().toString()).isEqualTo("editText1-filled");
    250     }
    251 
    252     @Test
    253     public void removeViewInBackground() throws Exception {
    254         activityToBackgroundShouldNotTriggerSave(
    255                 () -> mActivity.syncRunOnUiThread(() -> {
    256                     // Do not trigger new partition when switching to editText2
    257                     mEditText2.setFocusable(false);
    258 
    259                     mParent.removeView(mEditText1);
    260                 }),
    261                 null);
    262     }
    263 
    264     @Test
    265     public void hideViewInBackground() throws Exception {
    266         activityToBackgroundShouldNotTriggerSave(() -> {
    267                     // Do not trigger new partition when switching to editText2
    268                     mEditText2.setFocusable(false);
    269 
    270                     mEditText1.setVisibility(ViewGroup.INVISIBLE);
    271                 },
    272                 null);
    273     }
    274 
    275     @Test
    276     public void hideParentInBackground() throws Exception {
    277         activityToBackgroundShouldNotTriggerSave(() -> mParent.setVisibility(ViewGroup.INVISIBLE),
    278                 null);
    279     }
    280 
    281     @Test
    282     public void removeParentInBackground() throws Exception {
    283         activityToBackgroundShouldNotTriggerSave(
    284                 () -> ((ViewGroup) mParent.getParent()).removeView(mParent),
    285                 null);
    286     }
    287 
    288     @Test
    289     public void removeViewAfterBackground() throws Exception {
    290         activityToBackgroundShouldNotTriggerSave(() -> {
    291                     // Do not trigger new fill request when closing activity
    292                     mEditText1.setFocusable(false);
    293                     mEditText2.setFocusable(false);
    294                 },
    295                 () -> mParent.removeView(mEditText1));
    296     }
    297 }
    298