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.CannedFillResponse.NO_RESPONSE; 20 import static android.autofillservice.cts.FragmentContainerActivity.FRAGMENT_TAG; 21 import static android.autofillservice.cts.Helper.findNodeByResourceId; 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.assist.AssistStructure; 27 import android.app.assist.AssistStructure.ViewNode; 28 import android.os.Bundle; 29 import android.util.Log; 30 import android.view.autofill.AutofillValue; 31 import android.widget.EditText; 32 33 import org.junit.Before; 34 import org.junit.Rule; 35 import org.junit.Test; 36 37 public class MultipleFragmentLoginTest extends AutoFillServiceTestCase { 38 private static final String LOG_TAG = MultipleFragmentLoginTest.class.getSimpleName(); 39 @Rule 40 public final AutofillActivityTestRule<FragmentContainerActivity> mActivityRule = 41 new AutofillActivityTestRule<>(FragmentContainerActivity.class); 42 private FragmentContainerActivity mActivity; 43 private EditText mEditText1; 44 private EditText mEditText2; 45 46 @Before init()47 public void init() { 48 mActivity = mActivityRule.getActivity(); 49 mEditText1 = mActivity.findViewById(R.id.editText1); 50 mEditText2 = mActivity.findViewById(R.id.editText2); 51 } 52 53 @Test loginOnTwoFragments()54 public void loginOnTwoFragments() throws Exception { 55 enableService(); 56 57 Bundle clientState = new Bundle(); 58 clientState.putString("key", "value1"); 59 sReplier.addResponse(new CannedFillResponse.Builder() 60 .addDataset(new CannedFillResponse.CannedDataset.Builder() 61 .setField("editText1", "editText1-autofilled") 62 .setPresentation(createPresentation("dataset1")) 63 .build()) 64 .setExtras(clientState) 65 .build()); 66 67 final InstrumentedAutoFillService.FillRequest[] fillRequest = 68 new InstrumentedAutoFillService.FillRequest[1]; 69 70 // Trigger autofill 71 mActivity.syncRunOnUiThread(() -> { 72 mEditText2.requestFocus(); 73 mEditText1.requestFocus(); 74 }); 75 76 fillRequest[0] = sReplier.getNextFillRequest(); 77 78 assertThat(fillRequest[0].data).isNull(); 79 80 AssistStructure structure = fillRequest[0].contexts.get(0).getStructure(); 81 assertThat(fillRequest[0].contexts.size()).isEqualTo(1); 82 assertThat(findNodeByResourceId(structure, "editText1")).isNotNull(); 83 assertThat(findNodeByResourceId(structure, "editText2")).isNotNull(); 84 assertThat(findNodeByResourceId(structure, "editText3")).isNull(); 85 assertThat(findNodeByResourceId(structure, "editText4")).isNull(); 86 assertThat(findNodeByResourceId(structure, "editText5")).isNull(); 87 88 // Wait until autofill has been applied 89 mUiBot.selectDataset("dataset1"); 90 mUiBot.assertShownByText("editText1-autofilled"); 91 92 // Manually fill view 93 mActivity.syncRunOnUiThread(() -> mEditText2.setText("editText2-manually-filled")); 94 95 // Replacing the fragment focused a previously unknown view which triggers a new 96 // partition 97 clientState.putString("key", "value2"); 98 sReplier.addResponse(new CannedFillResponse.Builder() 99 .addDataset(new CannedFillResponse.CannedDataset.Builder() 100 .setField("editText3", "editText3-autofilled") 101 .setField("editText4", "editText4-autofilled") 102 .setPresentation(createPresentation("dataset2")) 103 .build()) 104 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText2", "editText5") 105 .setExtras(clientState) 106 .build()); 107 108 Log.i(LOG_TAG, "Switching Fragments"); 109 mActivity.syncRunOnUiThread( 110 () -> mActivity.getFragmentManager().beginTransaction().replace( 111 R.id.rootContainer, new FragmentWithMoreEditTexts(), 112 FRAGMENT_TAG).commitNow()); 113 EditText editText5 = mActivity.findViewById(R.id.editText5); 114 fillRequest[0] = sReplier.getNextFillRequest(); 115 116 // The fillRequest should have a fillContext for each partition. The first partition 117 // should be filled in 118 assertThat(fillRequest[0].contexts.size()).isEqualTo(2); 119 120 assertThat(fillRequest[0].data.getString("key")).isEqualTo("value1"); 121 122 AssistStructure structure1 = fillRequest[0].contexts.get(0).getStructure(); 123 ViewNode editText1Node = findNodeByResourceId(structure1, "editText1"); 124 // The actual value in the structure is not updated in FillRequest-contexts, but the 125 // autofill value is. For text views in SaveRequest both are updated, but this is the 126 // only exception. 127 assertThat(editText1Node.getAutofillValue()).isEqualTo( 128 AutofillValue.forText("editText1-autofilled")); 129 130 ViewNode editText2Node = findNodeByResourceId(structure1, "editText2"); 131 // Manually filled fields are not send to onFill. They appear in onSave if they are set 132 // as saveable fields. 133 assertThat(editText2Node.getText().toString()).isEqualTo(""); 134 135 assertThat(findNodeByResourceId(structure1, "editText3")).isNull(); 136 assertThat(findNodeByResourceId(structure1, "editText4")).isNull(); 137 assertThat(findNodeByResourceId(structure1, "editText5")).isNull(); 138 139 AssistStructure structure2 = fillRequest[0].contexts.get(1).getStructure(); 140 141 assertThat(findNodeByResourceId(structure2, "editText1")).isNull(); 142 assertThat(findNodeByResourceId(structure2, "editText2")).isNull(); 143 assertThat(findNodeByResourceId(structure2, "editText3")).isNotNull(); 144 assertThat(findNodeByResourceId(structure2, "editText4")).isNotNull(); 145 assertThat(findNodeByResourceId(structure2, "editText5")).isNotNull(); 146 147 // Wait until autofill has been applied 148 mUiBot.selectDataset("dataset2"); 149 mUiBot.assertShownByText("editText3-autofilled"); 150 mUiBot.assertShownByText("editText4-autofilled"); 151 152 // Manually fill view 153 mActivity.syncRunOnUiThread(() -> editText5.setText("editText5-manually-filled")); 154 155 // Finish activity and save data 156 mActivity.finish(); 157 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 158 159 // The saveRequest should have a fillContext for each partition with all the data 160 InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest(); 161 assertThat(saveRequest.contexts.size()).isEqualTo(2); 162 163 assertThat(saveRequest.data.getString("key")).isEqualTo("value2"); 164 165 structure1 = saveRequest.contexts.get(0).getStructure(); 166 editText1Node = findNodeByResourceId(structure1, "editText1"); 167 assertThat(editText1Node.getText().toString()).isEqualTo("editText1-autofilled"); 168 169 editText2Node = findNodeByResourceId(structure1, "editText2"); 170 assertThat(editText2Node.getText().toString()).isEqualTo("editText2-manually-filled"); 171 172 assertThat(findNodeByResourceId(structure1, "editText3")).isNull(); 173 assertThat(findNodeByResourceId(structure1, "editText4")).isNull(); 174 assertThat(findNodeByResourceId(structure1, "editText5")).isNull(); 175 176 structure2 = saveRequest.contexts.get(1).getStructure(); 177 assertThat(findNodeByResourceId(structure2, "editText1")).isNull(); 178 assertThat(findNodeByResourceId(structure2, "editText2")).isNull(); 179 180 ViewNode editText3Node = findNodeByResourceId(structure2, "editText3"); 181 assertThat(editText3Node.getText().toString()).isEqualTo("editText3-autofilled"); 182 183 ViewNode editText4Node = findNodeByResourceId(structure2, "editText4"); 184 assertThat(editText4Node.getText().toString()).isEqualTo("editText4-autofilled"); 185 186 ViewNode editText5Node = findNodeByResourceId(structure2, "editText5"); 187 assertThat(editText5Node.getText().toString()).isEqualTo("editText5-manually-filled"); 188 } 189 190 @Test uiDismissedWhenNonSavableFragmentIsGone()191 public void uiDismissedWhenNonSavableFragmentIsGone() throws Exception { 192 uiDismissedWhenFragmentIsGoneText(false); 193 } 194 195 @Test uiDismissedWhenSavableFragmentIsGone()196 public void uiDismissedWhenSavableFragmentIsGone() throws Exception { 197 uiDismissedWhenFragmentIsGoneText(true); 198 } 199 uiDismissedWhenFragmentIsGoneText(boolean savable)200 private void uiDismissedWhenFragmentIsGoneText(boolean savable) throws Exception { 201 // Set service. 202 enableService(); 203 204 // Set expectations. 205 final CannedFillResponse.Builder response = new CannedFillResponse.Builder() 206 .addDataset(new CannedFillResponse.CannedDataset.Builder() 207 .setField("editText1", "whatever") 208 .setPresentation(createPresentation("dataset1")) 209 .build()); 210 if (savable) { 211 response.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText2"); 212 } 213 214 sReplier.addResponse(response.build()); 215 216 // Trigger autofill 217 mActivity.syncRunOnUiThread(() -> { 218 mEditText2.requestFocus(); 219 mEditText1.requestFocus(); 220 }); 221 222 // Check UI is shown, but don't select it. 223 sReplier.getNextFillRequest(); 224 mUiBot.assertDatasets("dataset1"); 225 226 // Switch fragments 227 sReplier.addResponse(NO_RESPONSE); 228 mActivity.syncRunOnUiThread( 229 () -> mActivity.getFragmentManager().beginTransaction().replace( 230 R.id.rootContainer, new FragmentWithMoreEditTexts(), 231 FRAGMENT_TAG).commitNow()); 232 // Make sure UI is gone. 233 sReplier.getNextFillRequest(); 234 mUiBot.assertNoDatasets(); 235 } 236 237 // TODO: add similar tests for fragment with virtual view 238 } 239