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