1 /*
2  * Copyright (C) 2018 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.view.autofill;
18 
19 import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN;
20 import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
21 
22 import android.perftests.utils.BenchmarkState;
23 import android.perftests.utils.PerfTestActivity;
24 import android.view.View;
25 import android.widget.EditText;
26 
27 import androidx.test.filters.LargeTest;
28 
29 import com.android.perftests.autofill.R;
30 
31 import org.junit.Test;
32 
33 @LargeTest
34 public class LoginTest extends AbstractAutofillPerfTestCase {
35 
36     public static final String ID_USERNAME = "username";
37     public static final String ID_PASSWORD = "password";
38 
39     private EditText mUsername;
40     private EditText mPassword;
41     private AutofillManager mAfm;
42 
LoginTest()43     public LoginTest() {
44         super(R.layout.test_autofill_login);
45     }
46 
47     @Override
onCreate(PerfTestActivity activity)48     protected void onCreate(PerfTestActivity activity) {
49         View root = activity.getWindow().getDecorView();
50         mUsername = root.findViewById(R.id.username);
51         mPassword = root.findViewById(R.id.password);
52         mAfm = activity.getSystemService(AutofillManager.class);
53     }
54 
55     /**
56      * This is the baseline test for focusing the 2 views when autofill is disabled.
57      */
58     @Test
testFocus_noService()59     public void testFocus_noService() throws Throwable {
60         mTestWatcher.resetAutofillService();
61 
62         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
63         while (state.keepRunning()) {
64             mActivityRule.runOnUiThread(() -> {
65                 mUsername.requestFocus();
66                 mPassword.requestFocus();
67             });
68         }
69     }
70 
71     /**
72      * This time the service is called, but it returns a {@code null} response so the UI behaves
73      * as if autofill was disabled.
74      */
75     @Test
testFocus_serviceDoesNotAutofill()76     public void testFocus_serviceDoesNotAutofill() throws Throwable {
77         MyAutofillService.newCannedResponse().reply();
78         mTestWatcher.setAutofillService();
79 
80         // Must first focus in a field to trigger autofill and wait for service response
81         // outside the loop
82         mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
83         mTestWatcher.waitServiceConnect();
84         MyAutofillService.getLastFillRequest();
85         // Then focus on password so loop start with focus away from username
86         mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
87 
88         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
89         while (state.keepRunning()) {
90             mActivityRule.runOnUiThread(() -> {
91                 mUsername.requestFocus();
92                 mPassword.requestFocus();
93             });
94         }
95     }
96 
97     /**
98      * Now the service returns autofill data, for both username and password.
99      */
100     @Test
testFocus_autofillBothFields()101     public void testFocus_autofillBothFields() throws Throwable {
102         MyAutofillService.newCannedResponse()
103                 .setUsername(ID_USERNAME, "user")
104                 .setPassword(ID_PASSWORD, "pass")
105                 .reply();
106         mTestWatcher.setAutofillService();
107 
108         // Callback is used to slow down the calls made to the autofill server so the
109         // app is not crashed due to binder exhaustion. But the time spent waiting for the callbacks
110         // is not measured here...
111         MyAutofillCallback callback = new MyAutofillCallback();
112         mAfm.registerCallback(callback);
113 
114         // Must first trigger autofill and wait for service response outside the loop
115         mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
116         mTestWatcher.waitServiceConnect();
117         MyAutofillService.getLastFillRequest();
118         callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
119 
120         // Then focus on password so loop start with focus away from username
121         mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
122         callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
123         callback.expectEvent(mPassword, EVENT_INPUT_SHOWN);
124 
125 
126         // NOTE: we cannot run the whole loop inside the UI thread, because the autofill callback
127         // is called on it, which would cause a deadlock on expectEvent().
128         try {
129             BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
130             while (state.keepRunning()) {
131                 mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
132                 state.pauseTiming(); // Ignore time spent waiting for callbacks
133                 callback.expectEvent(mPassword, EVENT_INPUT_HIDDEN);
134                 callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
135                 state.resumeTiming();
136                 mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
137                 state.pauseTiming(); // Ignore time spent waiting for callbacks
138                 callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
139                 callback.expectEvent(mPassword, EVENT_INPUT_SHOWN);
140                 state.resumeTiming();
141             }
142 
143             // Sanity check
144             callback.assertNoAsyncErrors();
145         } finally {
146             mAfm.unregisterCallback(callback);
147         }
148     }
149 
150     /**
151      * Now the service returns autofill data, but just for username.
152      */
153     @Test
testFocus_autofillUsernameOnly()154     public void testFocus_autofillUsernameOnly() throws Throwable {
155         // Must set ignored ids so focus on password does not trigger new requests
156         MyAutofillService.newCannedResponse()
157                 .setUsername(ID_USERNAME, "user")
158                 .setIgnored(ID_PASSWORD)
159                 .reply();
160         mTestWatcher.setAutofillService();
161 
162         // Callback is used to slow down the calls made to the autofill server so the
163         // app is not crashed due to binder exhaustion. But the time spent waiting for the callbacks
164         // is not measured here...
165         MyAutofillCallback callback = new MyAutofillCallback();
166         mAfm.registerCallback(callback);
167 
168         // Must first trigger autofill and wait for service response outside the loop
169         mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
170         mTestWatcher.waitServiceConnect();
171         MyAutofillService.getLastFillRequest();
172         callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
173 
174         // Then focus on password so loop start with focus away from username
175         mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
176         callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
177 
178         // NOTE: we cannot run the whole loop inside the UI thread, because the autofill callback
179         // is called on it, which would cause a deadlock on expectEvent().
180         try {
181             BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
182             while (state.keepRunning()) {
183                 mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
184                 state.pauseTiming(); // Ignore time spent waiting for callbacks
185                 callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
186                 state.resumeTiming();
187                 mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
188                 state.pauseTiming(); // Ignore time spent waiting for callbacks
189                 callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
190                 state.resumeTiming();
191             }
192 
193             // Sanity check
194             callback.assertNoAsyncErrors();
195         } finally {
196             mAfm.unregisterCallback(callback);
197         }
198     }
199 
200     /**
201      * This is the baseline test for changing the 2 views when autofill is disabled.
202      */
203     @Test
testChange_noService()204     public void testChange_noService() throws Throwable {
205         mTestWatcher.resetAutofillService();
206 
207         changeTest(false);
208     }
209 
210     /**
211      * This time the service is called, but it returns a {@code null} response so the UI behaves
212      * as if autofill was disabled.
213      */
214     @Test
testChange_serviceDoesNotAutofill()215     public void testChange_serviceDoesNotAutofill() throws Throwable {
216         MyAutofillService.newCannedResponse().reply();
217         mTestWatcher.setAutofillService();
218 
219         changeTest(true);
220     }
221 
222     /**
223      * Now the service returns autofill data, for both username and password.
224      */
225     @Test
testChange_autofillBothFields()226     public void testChange_autofillBothFields() throws Throwable {
227         MyAutofillService.newCannedResponse()
228                 .setUsername(ID_USERNAME, "user")
229                 .setPassword(ID_PASSWORD, "pass")
230                 .reply();
231         mTestWatcher.setAutofillService();
232 
233         changeTest(true);
234     }
235 
236     /**
237      * Now the service returns autofill data, but just for username.
238      */
239     @Test
testChange_autofillUsernameOnly()240     public void testChange_autofillUsernameOnly() throws Throwable {
241         // Must set ignored ids so focus on password does not trigger new requests
242         MyAutofillService.newCannedResponse()
243                 .setUsername(ID_USERNAME, "user")
244                 .setIgnored(ID_PASSWORD)
245                 .reply();
246         mTestWatcher.setAutofillService();
247 
248         changeTest(true);
249     }
250 
changeTest(boolean waitForService)251     private void changeTest(boolean waitForService) throws Throwable {
252         // Must first focus in a field to trigger autofill and wait for service response
253         // outside the loop
254         mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
255         if (waitForService) {
256             mTestWatcher.waitServiceConnect();
257             MyAutofillService.getLastFillRequest();
258         }
259         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
260         while (state.keepRunning()) {
261             mActivityRule.runOnUiThread(() -> {
262                 mUsername.setText("");
263                 mUsername.setText("a");
264                 mPassword.setText("");
265                 mPassword.setText("x");
266             });
267         }
268     }
269 
270     @Test
testCallbacks()271     public void testCallbacks() throws Throwable {
272         MyAutofillService.newCannedResponse()
273                 .setUsername(ID_USERNAME, "user")
274                 .setPassword(ID_PASSWORD, "pass")
275                 .reply();
276         mTestWatcher.setAutofillService();
277 
278         MyAutofillCallback callback = new MyAutofillCallback();
279         mAfm.registerCallback(callback);
280 
281         // Must first focus in a field to trigger autofill and wait for service response
282         // outside the loop
283         mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
284         mTestWatcher.waitServiceConnect();
285         MyAutofillService.getLastFillRequest();
286         callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
287 
288         // Now focus on password to prepare loop state
289         mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
290         callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
291         callback.expectEvent(mPassword, EVENT_INPUT_SHOWN);
292 
293         // NOTE: we cannot run the whole loop inside the UI thread, because the autofill callback
294         // is called on it, which would cause a deadlock on expectEvent().
295         try {
296             BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
297             while (state.keepRunning()) {
298                 mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
299                 callback.expectEvent(mPassword, EVENT_INPUT_HIDDEN);
300                 callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
301                 mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
302                 callback.expectEvent(mUsername, EVENT_INPUT_HIDDEN);
303                 callback.expectEvent(mPassword, EVENT_INPUT_SHOWN);
304             }
305 
306             // Sanity check
307             callback.assertNoAsyncErrors();
308         } finally {
309             mAfm.unregisterCallback(callback);
310         }
311     }
312 }
313