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