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