1 /* 2 * Copyright 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 package android.autofillservice.cts; 17 18 import static android.autofillservice.cts.Helper.assertFillEventForContextCommitted; 19 import static android.autofillservice.cts.Helper.assertFillEventForFieldsClassification; 20 import static android.provider.Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION; 21 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT; 22 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE; 23 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE; 24 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH; 25 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH; 26 27 import static com.google.common.truth.Truth.assertThat; 28 29 import android.autofillservice.cts.Helper.FieldClassificationResult; 30 import android.autofillservice.cts.common.SettingsStateChangerRule; 31 import android.content.Context; 32 import android.platform.test.annotations.AppModeFull; 33 import android.service.autofill.FillEventHistory.Event; 34 import android.service.autofill.UserData; 35 import android.support.test.InstrumentationRegistry; 36 import android.view.autofill.AutofillId; 37 import android.view.autofill.AutofillManager; 38 import android.widget.EditText; 39 40 import org.junit.Before; 41 import org.junit.ClassRule; 42 import org.junit.Rule; 43 import org.junit.Test; 44 45 import java.util.List; 46 47 @AppModeFull // Service-specific test 48 public class FieldsClassificationTest extends AutoFillServiceTestCase { 49 50 private static final Context sContext = InstrumentationRegistry.getContext(); 51 52 @ClassRule 53 public static final SettingsStateChangerRule sFeatureEnabler = 54 new SettingsStateChangerRule(sContext, AUTOFILL_FEATURE_FIELD_CLASSIFICATION, "1"); 55 56 @ClassRule 57 public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger = 58 new SettingsStateChangerRule(sContext, 59 AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10"); 60 61 @ClassRule 62 public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger = 63 new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "9"); 64 65 @ClassRule 66 public static final SettingsStateChangerRule sUserDataMinValueChanger = 67 new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "5"); 68 69 @ClassRule 70 public static final SettingsStateChangerRule sUserDataMaxValueChanger = 71 new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50"); 72 73 @ClassRule 74 public static final SettingsStateChangerRule sUserDataMaxCategoryChanger = 75 new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "42"); 76 77 @Rule 78 public final AutofillActivityTestRule<GridActivity> mActivityRule = 79 new AutofillActivityTestRule<GridActivity>(GridActivity.class); 80 81 82 private GridActivity mActivity; 83 private AutofillManager mAfm; 84 85 @Before setFixtures()86 public void setFixtures() { 87 mActivity = mActivityRule.getActivity(); 88 mAfm = mActivity.getAutofillManager(); 89 } 90 91 @Test testFeatureIsEnabled()92 public void testFeatureIsEnabled() throws Exception { 93 enableService(); 94 assertThat(mAfm.isFieldClassificationEnabled()).isTrue(); 95 96 disableService(); 97 assertThat(mAfm.isFieldClassificationEnabled()).isFalse(); 98 } 99 100 @Test testGetAlgorithm()101 public void testGetAlgorithm() throws Exception { 102 enableService(); 103 104 // Check algorithms 105 final List<String> names = mAfm.getAvailableFieldClassificationAlgorithms(); 106 assertThat(names.size()).isAtLeast(1); 107 final String defaultAlgorithm = getDefaultAlgorithm(); 108 assertThat(defaultAlgorithm).isNotEmpty(); 109 assertThat(names).contains(defaultAlgorithm); 110 111 // Checks invalid service 112 disableService(); 113 assertThat(mAfm.getAvailableFieldClassificationAlgorithms()).isEmpty(); 114 } 115 116 @Test testUserData()117 public void testUserData() throws Exception { 118 assertThat(mAfm.getUserData()).isNull(); 119 assertThat(mAfm.getUserDataId()).isNull(); 120 121 enableService(); 122 mAfm.setUserData(new UserData.Builder("user_data_id", "value", "remote_id") 123 .build()); 124 assertThat(mAfm.getUserDataId()).isEqualTo("user_data_id"); 125 final UserData userData = mAfm.getUserData(); 126 assertThat(userData.getId()).isEqualTo("user_data_id"); 127 assertThat(userData.getFieldClassificationAlgorithm()).isNull(); 128 129 disableService(); 130 assertThat(mAfm.getUserData()).isNull(); 131 assertThat(mAfm.getUserDataId()).isNull(); 132 } 133 134 @Test testUserDataConstraints()135 public void testUserDataConstraints() throws Exception { 136 // NOTE: values set by the SettingsStateChangerRule @Rules should have unique values to 137 // make sure the getters below are reading the right property. 138 assertThat(UserData.getMaxFieldClassificationIdsSize()).isEqualTo(10); 139 assertThat(UserData.getMaxUserDataSize()).isEqualTo(9); 140 assertThat(UserData.getMinValueLength()).isEqualTo(5); 141 assertThat(UserData.getMaxValueLength()).isEqualTo(50); 142 assertThat(UserData.getMaxCategoryCount()).isEqualTo(42); 143 } 144 145 @Test testHit_oneUserData_oneDetectableField()146 public void testHit_oneUserData_oneDetectableField() throws Exception { 147 simpleHitTest(false, null); 148 } 149 150 @Test testHit_invalidAlgorithmIsIgnored()151 public void testHit_invalidAlgorithmIsIgnored() throws Exception { 152 // For simplicity's sake, let's assume that name will never be valid.. 153 String invalidName = " ALGORITHM, Y NO INVALID? "; 154 155 simpleHitTest(true, invalidName); 156 } 157 158 @Test testHit_userDataAlgorithmIsReset()159 public void testHit_userDataAlgorithmIsReset() throws Exception { 160 simpleHitTest(true, null); 161 } 162 simpleHitTest(boolean setAlgorithm, String algorithm)163 private void simpleHitTest(boolean setAlgorithm, String algorithm) throws Exception { 164 // Set service. 165 enableService(); 166 167 // Set expectations. 168 final UserData.Builder userData = new UserData.Builder("id", "FULLY", "myId"); 169 if (setAlgorithm) { 170 userData.setFieldClassificationAlgorithm(algorithm, null); 171 } 172 mAfm.setUserData(userData.build()); 173 final MyAutofillCallback callback = mActivity.registerCallback(); 174 final EditText field = mActivity.getCell(1, 1); 175 final AutofillId fieldId = field.getAutofillId(); 176 sReplier.addResponse(new CannedFillResponse.Builder() 177 .setFieldClassificationIds(fieldId) 178 .build()); 179 180 // Trigger autofill 181 mActivity.focusCell(1, 1); 182 sReplier.getNextFillRequest(); 183 184 mUiBot.assertNoDatasetsEver(); 185 callback.assertUiUnavailableEvent(field); 186 187 // Simulate user input 188 mActivity.setText(1, 1, "fully"); 189 190 // Finish context. 191 mAfm.commit(); 192 193 // Assert results 194 final List<Event> events = InstrumentedAutoFillService.getFillEvents(1); 195 assertFillEventForFieldsClassification(events.get(0), fieldId, "myId", 1); 196 } 197 198 @Test testHit_manyUserData_oneDetectableField_bestMatchIsFirst()199 public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception { 200 manyUserData_oneDetectableField(true); 201 } 202 203 @Test testHit_manyUserData_oneDetectableField_bestMatchIsSecond()204 public void testHit_manyUserData_oneDetectableField_bestMatchIsSecond() throws Exception { 205 manyUserData_oneDetectableField(false); 206 } 207 manyUserData_oneDetectableField(boolean firstMatch)208 private void manyUserData_oneDetectableField(boolean firstMatch) throws Exception { 209 // Set service. 210 enableService(); 211 212 // Set expectations. 213 mAfm.setUserData(new UserData.Builder("id", "Iam1ST", "1stId") 214 .add("Iam2ND", "2ndId").build()); 215 final MyAutofillCallback callback = mActivity.registerCallback(); 216 final EditText field = mActivity.getCell(1, 1); 217 final AutofillId fieldId = field.getAutofillId(); 218 sReplier.addResponse(new CannedFillResponse.Builder() 219 .setFieldClassificationIds(fieldId) 220 .build()); 221 222 // Trigger autofill 223 mActivity.focusCell(1, 1); 224 sReplier.getNextFillRequest(); 225 226 mUiBot.assertNoDatasetsEver(); 227 callback.assertUiUnavailableEvent(field); 228 229 // Simulate user input 230 mActivity.setText(1, 1, firstMatch ? "IAM111" : "IAM222"); 231 232 // Finish context. 233 mAfm.commit(); 234 235 // Assert results 236 final List<Event> events = InstrumentedAutoFillService.getFillEvents(1); 237 // Best match is 0.66 (4 of 6), worst is 0.5 (3 of 6) 238 if (firstMatch) { 239 assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] { 240 new FieldClassificationResult(fieldId, new String[] { "1stId", "2ndId" }, 241 new float[] { 0.66F, 0.5F })}); 242 } else { 243 assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] { 244 new FieldClassificationResult(fieldId, new String[] { "2ndId", "1stId" }, 245 new float[] { 0.66F, 0.5F }) }); 246 } 247 } 248 249 @Test testHit_oneUserData_manyDetectableFields()250 public void testHit_oneUserData_manyDetectableFields() throws Exception { 251 // Set service. 252 enableService(); 253 254 // Set expectations. 255 mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build()); 256 final MyAutofillCallback callback = mActivity.registerCallback(); 257 final EditText field1 = mActivity.getCell(1, 1); 258 final AutofillId fieldId1 = field1.getAutofillId(); 259 final EditText field2 = mActivity.getCell(1, 2); 260 final AutofillId fieldId2 = field2.getAutofillId(); 261 sReplier.addResponse(new CannedFillResponse.Builder() 262 .setFieldClassificationIds(fieldId1, fieldId2) 263 .build()); 264 265 // Trigger autofill 266 mActivity.focusCell(1, 1); 267 sReplier.getNextFillRequest(); 268 269 mUiBot.assertNoDatasetsEver(); 270 callback.assertUiUnavailableEvent(field1); 271 272 // Simulate user input 273 mActivity.setText(1, 1, "fully"); // 100% 274 mActivity.setText(1, 2, "fooly"); // 60% 275 276 // Finish context. 277 mAfm.commit(); 278 279 // Assert results 280 final List<Event> events = InstrumentedAutoFillService.getFillEvents(1); 281 assertFillEventForFieldsClassification(events.get(0), 282 new FieldClassificationResult[] { 283 new FieldClassificationResult(fieldId1, "myId", 1.0F), 284 new FieldClassificationResult(fieldId2, "myId", 0.6F), 285 }); 286 } 287 288 @Test testHit_manyUserData_manyDetectableFields()289 public void testHit_manyUserData_manyDetectableFields() throws Exception { 290 // Set service. 291 enableService(); 292 293 // Set expectations. 294 mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId") 295 .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any 296 .add("EMPTY", "otherId") 297 .build()); 298 final MyAutofillCallback callback = mActivity.registerCallback(); 299 final EditText field1 = mActivity.getCell(1, 1); 300 final AutofillId fieldId1 = field1.getAutofillId(); 301 final EditText field2 = mActivity.getCell(1, 2); 302 final AutofillId fieldId2 = field2.getAutofillId(); 303 final EditText field3 = mActivity.getCell(2, 1); 304 final AutofillId fieldId3 = field3.getAutofillId(); 305 final EditText field4 = mActivity.getCell(2, 2); 306 final AutofillId fieldId4 = field4.getAutofillId(); 307 sReplier.addResponse(new CannedFillResponse.Builder() 308 .setFieldClassificationIds(fieldId1, fieldId2) 309 .build()); 310 311 // Trigger autofill 312 mActivity.focusCell(1, 1); 313 sReplier.getNextFillRequest(); 314 315 mUiBot.assertNoDatasetsEver(); 316 callback.assertUiUnavailableEvent(field1); 317 318 // Simulate user input 319 mActivity.setText(1, 1, "fully"); // u1: 100% u2: 20% 320 mActivity.setText(1, 2, "empty"); // u1: 20% u2: 100% 321 mActivity.setText(2, 1, "fooly"); // u1: 60% u2: 20% 322 mActivity.setText(2, 2, "emppy"); // u1: 20% u2: 80% 323 324 // Finish context. 325 mAfm.commit(); 326 327 // Assert results 328 final List<Event> events = InstrumentedAutoFillService.getFillEvents(1); 329 assertFillEventForFieldsClassification(events.get(0), 330 new FieldClassificationResult[] { 331 new FieldClassificationResult(fieldId1, new String[] { "myId", "otherId" }, 332 new float[] { 1.0F, 0.2F }), 333 new FieldClassificationResult(fieldId2, new String[] { "otherId", "myId" }, 334 new float[] { 1.0F, 0.2F }), 335 new FieldClassificationResult(fieldId3, new String[] { "myId", "otherId" }, 336 new float[] { 0.6F, 0.2F }), 337 new FieldClassificationResult(fieldId4, new String[] { "otherId", "myId"}, 338 new float[] { 0.80F, 0.2F })}); 339 } 340 341 @Test testHit_manyUserDataPerField_manyDetectableFields()342 public void testHit_manyUserDataPerField_manyDetectableFields() throws Exception { 343 // Set service. 344 enableService(); 345 346 // Set expectations. 347 mAfm.setUserData(new UserData.Builder("id", "zzzzz", "myId") // should not have matched any 348 .add("FULL1", "myId") // match 80%, should not have been reported 349 .add("FULLY", "myId") // match 100% 350 .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any 351 .add("EMPTY", "otherId") 352 .build()); 353 final MyAutofillCallback callback = mActivity.registerCallback(); 354 final EditText field1 = mActivity.getCell(1, 1); 355 final AutofillId fieldId1 = field1.getAutofillId(); 356 final EditText field2 = mActivity.getCell(1, 2); 357 final AutofillId fieldId2 = field2.getAutofillId(); 358 final EditText field3 = mActivity.getCell(2, 1); 359 final AutofillId fieldId3 = field3.getAutofillId(); 360 final EditText field4 = mActivity.getCell(2, 2); 361 final AutofillId fieldId4 = field4.getAutofillId(); 362 sReplier.addResponse(new CannedFillResponse.Builder() 363 .setFieldClassificationIds(fieldId1, fieldId2) 364 .build()); 365 366 // Trigger autofill 367 mActivity.focusCell(1, 1); 368 sReplier.getNextFillRequest(); 369 370 mUiBot.assertNoDatasetsEver(); 371 callback.assertUiUnavailableEvent(field1); 372 373 // Simulate user input 374 mActivity.setText(1, 1, "fully"); // u1: 100% u2: 20% 375 mActivity.setText(1, 2, "empty"); // u1: 20% u2: 100% 376 mActivity.setText(2, 1, "fooly"); // u1: 60% u2: 20% 377 mActivity.setText(2, 2, "emppy"); // u1: 20% u2: 80% 378 379 // Finish context. 380 mAfm.commit(); 381 382 // Assert results 383 final List<Event> events = InstrumentedAutoFillService.getFillEvents(1); 384 assertFillEventForFieldsClassification(events.get(0), 385 new FieldClassificationResult[] { 386 new FieldClassificationResult(fieldId1, new String[] { "myId", "otherId" }, 387 new float[] { 1.0F, 0.2F }), 388 new FieldClassificationResult(fieldId2, new String[] { "otherId", "myId" }, 389 new float[] { 1.0F, 0.2F }), 390 new FieldClassificationResult(fieldId3, new String[] { "myId", "otherId" }, 391 new float[] { 0.6F, 0.2F }), 392 new FieldClassificationResult(fieldId4, new String[] { "otherId", "myId"}, 393 new float[] { 0.80F, 0.2F })}); 394 } 395 396 @Test testMiss()397 public void testMiss() throws Exception { 398 // Set service. 399 enableService(); 400 401 // Set expectations. 402 mAfm.setUserData(new UserData.Builder("id", "ABCDEF", "myId").build()); 403 final MyAutofillCallback callback = mActivity.registerCallback(); 404 final EditText field = mActivity.getCell(1, 1); 405 final AutofillId fieldId = field.getAutofillId(); 406 sReplier.addResponse(new CannedFillResponse.Builder() 407 .setFieldClassificationIds(fieldId) 408 .build()); 409 410 // Trigger autofill 411 mActivity.focusCell(1, 1); 412 sReplier.getNextFillRequest(); 413 414 mUiBot.assertNoDatasetsEver(); 415 callback.assertUiUnavailableEvent(field); 416 417 // Simulate user input 418 mActivity.setText(1, 1, "xyz"); 419 420 // Finish context. 421 mAfm.commit(); 422 423 // Assert results 424 final List<Event> events = InstrumentedAutoFillService.getFillEvents(1); 425 assertFillEventForContextCommitted(events.get(0)); 426 } 427 428 @Test testNoUserInput()429 public void testNoUserInput() throws Exception { 430 // Set service. 431 enableService(); 432 433 // Set expectations. 434 mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build()); 435 final MyAutofillCallback callback = mActivity.registerCallback(); 436 final EditText field = mActivity.getCell(1, 1); 437 final AutofillId fieldId = field.getAutofillId(); 438 sReplier.addResponse(new CannedFillResponse.Builder() 439 .setFieldClassificationIds(fieldId) 440 .build()); 441 442 // Trigger autofill 443 mActivity.focusCell(1, 1); 444 sReplier.getNextFillRequest(); 445 446 mUiBot.assertNoDatasetsEver(); 447 callback.assertUiUnavailableEvent(field); 448 449 // Finish context. 450 mAfm.commit(); 451 452 // Assert results 453 final List<Event> events = InstrumentedAutoFillService.getFillEvents(1); 454 assertFillEventForContextCommitted(events.get(0)); 455 } 456 getDefaultAlgorithm()457 private String getDefaultAlgorithm() { 458 return mAfm.getDefaultFieldClassificationAlgorithm(); 459 } 460 461 /* 462 * TODO(b/73648631): other scenarios: 463 * 464 * - Multipartition (for example, one response with FieldsDetection, others with datasets, 465 * saveinfo, and/or ignoredIds) 466 * - make sure detectable fields don't trigger a new partition 467 * v test partial hit (for example, 'fool' instead of 'full' 468 * v multiple fields 469 * v multiple value 470 * - combinations of above items 471 */ 472 } 473