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.InstrumentedAutoFillService.SERVICE_NAME; 20 import static android.autofillservice.cts.UiBot.PORTRAIT; 21 import static android.autofillservice.cts.common.ShellHelper.runShellCommand; 22 import static android.provider.Settings.Secure.AUTOFILL_SERVICE; 23 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; 24 import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED; 25 import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED; 26 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED; 27 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED; 28 import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN; 29 30 import static com.google.common.truth.Truth.assertThat; 31 import static com.google.common.truth.Truth.assertWithMessage; 32 33 import android.app.Activity; 34 import android.app.assist.AssistStructure; 35 import android.app.assist.AssistStructure.ViewNode; 36 import android.app.assist.AssistStructure.WindowNode; 37 import android.autofillservice.cts.common.SettingsHelper; 38 import android.content.ComponentName; 39 import android.content.Context; 40 import android.content.pm.PackageManager; 41 import android.graphics.Bitmap; 42 import android.icu.util.Calendar; 43 import android.os.Bundle; 44 import android.os.Environment; 45 import android.service.autofill.FieldClassification; 46 import android.service.autofill.FieldClassification.Match; 47 import android.service.autofill.FillContext; 48 import android.service.autofill.FillEventHistory; 49 import android.support.test.InstrumentationRegistry; 50 import android.text.TextUtils; 51 import android.util.Log; 52 import android.util.Pair; 53 import android.view.View; 54 import android.view.ViewGroup; 55 import android.view.ViewStructure.HtmlInfo; 56 import android.view.autofill.AutofillId; 57 import android.view.autofill.AutofillManager.AutofillCallback; 58 import android.view.autofill.AutofillValue; 59 import android.webkit.WebView; 60 61 import androidx.annotation.NonNull; 62 import androidx.annotation.Nullable; 63 64 import com.android.compatibility.common.util.BitmapUtils; 65 import com.android.compatibility.common.util.RequiredFeatureRule; 66 67 import java.io.File; 68 import java.io.IOException; 69 import java.util.List; 70 import java.util.Map; 71 import java.util.Map.Entry; 72 import java.util.function.Function; 73 74 /** 75 * Helper for common funcionalities. 76 */ 77 final class Helper { 78 79 static final String TAG = "AutoFillCtsHelper"; 80 81 static final boolean VERBOSE = false; 82 83 static final String MY_PACKAGE = "android.autofillservice.cts"; 84 85 static final String ID_USERNAME_LABEL = "username_label"; 86 static final String ID_USERNAME = "username"; 87 static final String ID_PASSWORD_LABEL = "password_label"; 88 static final String ID_PASSWORD = "password"; 89 static final String ID_LOGIN = "login"; 90 static final String ID_OUTPUT = "output"; 91 static final String ID_STATIC_TEXT = "static_text"; 92 93 public static final String NULL_DATASET_ID = null; 94 95 /** 96 * Can be used in cases where the autofill values is required by irrelevant (like adding a 97 * value to an authenticated dataset). 98 */ 99 public static final String UNUSED_AUTOFILL_VALUE = null; 100 101 private static final String CMD_LIST_SESSIONS = "cmd autofill list sessions"; 102 103 private static final String ACCELLEROMETER_CHANGE = 104 "content insert --uri content://settings/system --bind name:s:accelerometer_rotation " 105 + "--bind value:i:%d"; 106 107 private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory() 108 + "/CtsAutoFillServiceTestCases"; 109 110 /** 111 * Helper interface used to filter nodes. 112 * 113 * @param <T> node type 114 */ 115 interface NodeFilter<T> { 116 /** 117 * Returns whether the node passes the filter for such given id. 118 */ matches(T node, Object id)119 boolean matches(T node, Object id); 120 } 121 122 private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> { 123 return id.equals(node.getIdEntry()); 124 }; 125 126 private static final NodeFilter<ViewNode> AUTOFILL_ID_FILTER = (node, id) -> { 127 return id.equals(node.getAutofillId()); 128 }; 129 130 private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> { 131 return id.equals(getHtmlName(node)); 132 }; 133 134 private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> { 135 return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry()); 136 }; 137 138 private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> { 139 return id.equals(node.getText()); 140 }; 141 142 private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> { 143 return hasHint(node.getAutofillHints(), id); 144 }; 145 146 private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> { 147 final String className = node.getClassName(); 148 if (!className.equals("android.webkit.WebView")) return false; 149 150 final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form"); 151 final String formName = getAttributeValue(htmlInfo, "name"); 152 return id.equals(formName); 153 }; 154 155 private static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> { 156 return hasHint(view.getAutofillHints(), id); 157 }; 158 159 /** 160 * Dump the assist structure on logcat. 161 */ dumpStructure(String message, AssistStructure structure)162 static void dumpStructure(String message, AssistStructure structure) { 163 final StringBuffer buffer = new StringBuffer(message) 164 .append(": component=") 165 .append(structure.getActivityComponent()); 166 final int nodes = structure.getWindowNodeCount(); 167 for (int i = 0; i < nodes; i++) { 168 final WindowNode windowNode = structure.getWindowNodeAt(i); 169 dump(buffer, windowNode.getRootViewNode(), " ", 0); 170 } 171 Log.i(TAG, buffer.toString()); 172 } 173 174 /** 175 * Dump the contexts on logcat. 176 */ dumpStructure(String message, List<FillContext> contexts)177 static void dumpStructure(String message, List<FillContext> contexts) { 178 for (FillContext context : contexts) { 179 dumpStructure(message, context.getStructure()); 180 } 181 } 182 183 /** 184 * Dumps the state of the autofill service on logcat. 185 */ dumpAutofillService()186 static void dumpAutofillService() { 187 Log.i(TAG, "dumpsys autofill\n\n" + runShellCommand("dumpsys autofill")); 188 } 189 190 /** 191 * Sets whether the user completed the initial setup. 192 */ setUserComplete(Context context, boolean complete)193 static void setUserComplete(Context context, boolean complete) { 194 SettingsHelper.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null); 195 } 196 dump(StringBuffer buffer, ViewNode node, String prefix, int childId)197 private static void dump(StringBuffer buffer, ViewNode node, String prefix, int childId) { 198 final int childrenSize = node.getChildCount(); 199 buffer.append("\n").append(prefix) 200 .append('#').append(childId).append(':') 201 .append("resId=").append(node.getIdEntry()) 202 .append(" class=").append(node.getClassName()) 203 .append(" text=").append(node.getText()) 204 .append(" class=").append(node.getClassName()) 205 .append(" webDomain=").append(node.getWebDomain()) 206 .append(" #children=").append(childrenSize); 207 208 buffer.append("\n").append(prefix) 209 .append(" afId=").append(node.getAutofillId()) 210 .append(" afType=").append(node.getAutofillType()) 211 .append(" afValue=").append(node.getAutofillValue()) 212 .append(" checked=").append(node.isChecked()) 213 .append(" focused=").append(node.isFocused()); 214 215 final HtmlInfo htmlInfo = node.getHtmlInfo(); 216 if (htmlInfo != null) { 217 buffer.append("\nHtmlInfo: tag=").append(htmlInfo.getTag()) 218 .append(", attrs: ").append(htmlInfo.getAttributes()); 219 } 220 221 prefix += " "; 222 if (childrenSize > 0) { 223 for (int i = 0; i < childrenSize; i++) { 224 dump(buffer, node.getChildAt(i), prefix, i); 225 } 226 } 227 } 228 229 /** 230 * Gets a node if it matches the filter criteria for the given id. 231 */ findNodeByFilter(@onNull AssistStructure structure, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)232 static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id, 233 @NonNull NodeFilter<ViewNode> filter) { 234 Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent()); 235 final int nodes = structure.getWindowNodeCount(); 236 for (int i = 0; i < nodes; i++) { 237 final WindowNode windowNode = structure.getWindowNodeAt(i); 238 final ViewNode rootNode = windowNode.getRootViewNode(); 239 final ViewNode node = findNodeByFilter(rootNode, id, filter); 240 if (node != null) { 241 return node; 242 } 243 } 244 return null; 245 } 246 247 /** 248 * Gets a node if it matches the filter criteria for the given id. 249 */ findNodeByFilter(@onNull List<FillContext> contexts, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)250 static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id, 251 @NonNull NodeFilter<ViewNode> filter) { 252 for (FillContext context : contexts) { 253 ViewNode node = findNodeByFilter(context.getStructure(), id, filter); 254 if (node != null) { 255 return node; 256 } 257 } 258 return null; 259 } 260 261 /** 262 * Gets a node if it matches the filter criteria for the given id. 263 */ findNodeByFilter(@onNull ViewNode node, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)264 static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id, 265 @NonNull NodeFilter<ViewNode> filter) { 266 if (filter.matches(node, id)) { 267 return node; 268 } 269 final int childrenSize = node.getChildCount(); 270 if (childrenSize > 0) { 271 for (int i = 0; i < childrenSize; i++) { 272 final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter); 273 if (found != null) { 274 return found; 275 } 276 } 277 } 278 return null; 279 } 280 281 /** 282 * Gets a node given its Android resource id, or {@code null} if not found. 283 */ findNodeByResourceId(AssistStructure structure, String resourceId)284 static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) { 285 return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER); 286 } 287 288 /** 289 * Gets a node given its Android resource id, or {@code null} if not found. 290 */ findNodeByResourceId(List<FillContext> contexts, String resourceId)291 static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) { 292 return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER); 293 } 294 295 /** 296 * Gets a node given its Android resource id, or {@code null} if not found. 297 */ findNodeByResourceId(ViewNode node, String resourceId)298 static ViewNode findNodeByResourceId(ViewNode node, String resourceId) { 299 return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER); 300 } 301 302 /** 303 * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found. 304 */ findNodeByHtmlName(AssistStructure structure, String htmlName)305 static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) { 306 return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER); 307 } 308 309 /** 310 * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found. 311 */ findNodeByHtmlName(List<FillContext> contexts, String htmlName)312 static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) { 313 return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER); 314 } 315 316 /** 317 * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found. 318 */ findNodeByHtmlName(ViewNode node, String htmlName)319 static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) { 320 return findNodeByFilter(node, htmlName, HTML_NAME_FILTER); 321 } 322 323 /** 324 * Gets a node given the value of its (single) autofill hint property, or {@code null} if not 325 * found. 326 */ findNodeByAutofillHint(ViewNode node, String hint)327 static ViewNode findNodeByAutofillHint(ViewNode node, String hint) { 328 return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER); 329 } 330 331 /** 332 * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if 333 * not found. 334 */ findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id)335 static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) { 336 return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER); 337 } 338 339 /** 340 * Gets the {@code name} attribute of a node representing an HTML input tag. 341 */ 342 @Nullable getHtmlName(@onNull ViewNode node)343 static String getHtmlName(@NonNull ViewNode node) { 344 final HtmlInfo htmlInfo = node.getHtmlInfo(); 345 if (htmlInfo == null) { 346 return null; 347 } 348 final String tag = htmlInfo.getTag(); 349 if (!"input".equals(tag)) { 350 Log.w(TAG, "getHtmlName(): invalid tag (" + tag + ") on " + htmlInfo); 351 return null; 352 } 353 for (Pair<String, String> attr : htmlInfo.getAttributes()) { 354 if ("name".equals(attr.first)) { 355 return attr.second; 356 } 357 } 358 Log.w(TAG, "getHtmlName(): no 'name' attribute on " + htmlInfo); 359 return null; 360 } 361 362 /** 363 * Gets a node given its expected text, or {@code null} if not found. 364 */ findNodeByText(AssistStructure structure, String text)365 static ViewNode findNodeByText(AssistStructure structure, String text) { 366 return findNodeByFilter(structure, text, TEXT_FILTER); 367 } 368 369 /** 370 * Gets a node given its expected text, or {@code null} if not found. 371 */ findNodeByText(ViewNode node, String text)372 static ViewNode findNodeByText(ViewNode node, String text) { 373 return findNodeByFilter(node, text, TEXT_FILTER); 374 } 375 376 /** 377 * Gets a view that contains the an autofill hint, or {@code null} if not found. 378 */ findViewByAutofillHint(Activity activity, String hint)379 static View findViewByAutofillHint(Activity activity, String hint) { 380 final View rootView = activity.getWindow().getDecorView().getRootView(); 381 return findViewByAutofillHint(rootView, hint); 382 } 383 384 /** 385 * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if 386 * not found. 387 */ findViewByAutofillHint(View view, String hint)388 static View findViewByAutofillHint(View view, String hint) { 389 if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view; 390 if ((view instanceof ViewGroup)) { 391 final ViewGroup group = (ViewGroup) view; 392 for (int i = 0; i < group.getChildCount(); i++) { 393 final View child = findViewByAutofillHint(group.getChildAt(i), hint); 394 if (child != null) return child; 395 } 396 } 397 return null; 398 } 399 400 /** 401 * Gets a view (or a descendant of it) that has the given {@code id}, or {@code null} if 402 * not found. 403 */ findNodeByAutofillId(AssistStructure structure, AutofillId id)404 static ViewNode findNodeByAutofillId(AssistStructure structure, AutofillId id) { 405 return findNodeByFilter(structure, id, AUTOFILL_ID_FILTER); 406 } 407 408 /** 409 * Asserts a text-based node is sanitized. 410 */ assertTextIsSanitized(ViewNode node)411 static void assertTextIsSanitized(ViewNode node) { 412 final CharSequence text = node.getText(); 413 final String resourceId = node.getIdEntry(); 414 if (!TextUtils.isEmpty(text)) { 415 throw new AssertionError("text on sanitized field " + resourceId + ": " + text); 416 } 417 418 assertNotFromResources(node); 419 assertNodeHasNoAutofillValue(node); 420 } 421 assertNotFromResources(ViewNode node)422 private static void assertNotFromResources(ViewNode node) { 423 assertThat(node.getTextIdEntry()).isNull(); 424 } 425 assertNodeHasNoAutofillValue(ViewNode node)426 static void assertNodeHasNoAutofillValue(ViewNode node) { 427 final AutofillValue value = node.getAutofillValue(); 428 if (value != null) { 429 final String text = value.isText() ? value.getTextValue().toString() : "N/A"; 430 throw new AssertionError("node has value: " + value + " text=" + text); 431 } 432 } 433 434 /** 435 * Asserts the contents of a text-based node that is also auto-fillable. 436 */ assertTextOnly(ViewNode node, String expectedValue)437 static void assertTextOnly(ViewNode node, String expectedValue) { 438 assertText(node, expectedValue, false); 439 assertNotFromResources(node); 440 } 441 442 /** 443 * Asserts the contents of a text-based node that is also auto-fillable. 444 */ assertTextOnly(AssistStructure structure, String resourceId, String expectedValue)445 static void assertTextOnly(AssistStructure structure, String resourceId, String expectedValue) { 446 final ViewNode node = findNodeByResourceId(structure, resourceId); 447 assertText(node, expectedValue, false); 448 assertNotFromResources(node); 449 } 450 451 /** 452 * Asserts the contents of a text-based node that is also auto-fillable. 453 */ assertTextAndValue(ViewNode node, String expectedValue)454 static void assertTextAndValue(ViewNode node, String expectedValue) { 455 assertText(node, expectedValue, true); 456 assertNotFromResources(node); 457 } 458 459 /** 460 * Asserts a text-based node exists and verify its values. 461 */ assertTextAndValue(AssistStructure structure, String resourceId, String expectedValue)462 static ViewNode assertTextAndValue(AssistStructure structure, String resourceId, 463 String expectedValue) { 464 final ViewNode node = findNodeByResourceId(structure, resourceId); 465 assertTextAndValue(node, expectedValue); 466 return node; 467 } 468 469 /** 470 * Asserts a text-based node exists and is sanitized. 471 */ assertValue(AssistStructure structure, String resourceId, String expectedValue)472 static ViewNode assertValue(AssistStructure structure, String resourceId, 473 String expectedValue) { 474 final ViewNode node = findNodeByResourceId(structure, resourceId); 475 assertTextValue(node, expectedValue); 476 return node; 477 } 478 479 /** 480 * Asserts the values of a text-based node whose string come from resoruces. 481 */ assertTextFromResouces(AssistStructure structure, String resourceId, String expectedValue, boolean isAutofillable, String expectedTextIdEntry)482 static ViewNode assertTextFromResouces(AssistStructure structure, String resourceId, 483 String expectedValue, boolean isAutofillable, String expectedTextIdEntry) { 484 final ViewNode node = findNodeByResourceId(structure, resourceId); 485 assertText(node, expectedValue, isAutofillable); 486 assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry); 487 return node; 488 } 489 assertText(ViewNode node, String expectedValue, boolean isAutofillable)490 private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) { 491 assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString()) 492 .isEqualTo(expectedValue); 493 final AutofillValue value = node.getAutofillValue(); 494 final AutofillId id = node.getAutofillId(); 495 if (isAutofillable) { 496 assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull(); 497 assertWithMessage("wrong auto-fill value on %s", id) 498 .that(value.getTextValue().toString()).isEqualTo(expectedValue); 499 } else { 500 assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull(); 501 } 502 } 503 504 /** 505 * Asserts the auto-fill value of a text-based node. 506 */ assertTextValue(ViewNode node, String expectedText)507 static ViewNode assertTextValue(ViewNode node, String expectedText) { 508 final AutofillValue value = node.getAutofillValue(); 509 final AutofillId id = node.getAutofillId(); 510 assertWithMessage("null autofill value on %s", id).that(value).isNotNull(); 511 assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue(); 512 assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString()) 513 .isEqualTo(expectedText); 514 return node; 515 } 516 517 /** 518 * Asserts the auto-fill value of a list-based node. 519 */ assertListValue(ViewNode node, int expectedIndex)520 static ViewNode assertListValue(ViewNode node, int expectedIndex) { 521 final AutofillValue value = node.getAutofillValue(); 522 final AutofillId id = node.getAutofillId(); 523 assertWithMessage("null autofill value on %s", id).that(value).isNotNull(); 524 assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue(); 525 assertWithMessage("wrong autofill value on %s", id).that(value.getListValue()) 526 .isEqualTo(expectedIndex); 527 return node; 528 } 529 530 /** 531 * Asserts the auto-fill value of a toggle-based node. 532 */ assertToggleValue(ViewNode node, boolean expectedToggle)533 static void assertToggleValue(ViewNode node, boolean expectedToggle) { 534 final AutofillValue value = node.getAutofillValue(); 535 final AutofillId id = node.getAutofillId(); 536 assertWithMessage("null autofill value on %s", id).that(value).isNotNull(); 537 assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue(); 538 assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue()) 539 .isEqualTo(expectedToggle); 540 } 541 542 /** 543 * Asserts the auto-fill value of a date-based node. 544 */ assertDateValue(Object object, AutofillValue value, int year, int month, int day)545 static void assertDateValue(Object object, AutofillValue value, int year, int month, int day) { 546 assertWithMessage("null autofill value on %s", object).that(value).isNotNull(); 547 assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue(); 548 549 final Calendar cal = Calendar.getInstance(); 550 cal.setTimeInMillis(value.getDateValue()); 551 552 assertWithMessage("Wrong year on AutofillValue %s", value) 553 .that(cal.get(Calendar.YEAR)).isEqualTo(year); 554 assertWithMessage("Wrong month on AutofillValue %s", value) 555 .that(cal.get(Calendar.MONTH)).isEqualTo(month); 556 assertWithMessage("Wrong day on AutofillValue %s", value) 557 .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day); 558 } 559 560 /** 561 * Asserts the auto-fill value of a date-based node. 562 */ assertDateValue(ViewNode node, int year, int month, int day)563 static void assertDateValue(ViewNode node, int year, int month, int day) { 564 assertDateValue(node, node.getAutofillValue(), year, month, day); 565 } 566 567 /** 568 * Asserts the auto-fill value of a date-based view. 569 */ assertDateValue(View view, int year, int month, int day)570 static void assertDateValue(View view, int year, int month, int day) { 571 assertDateValue(view, view.getAutofillValue(), year, month, day); 572 } 573 574 /** 575 * Asserts the auto-fill value of a time-based node. 576 */ assertTimeValue(Object object, AutofillValue value, int hour, int minute)577 private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) { 578 assertWithMessage("null autofill value on %s", object).that(value).isNotNull(); 579 assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue(); 580 581 final Calendar cal = Calendar.getInstance(); 582 cal.setTimeInMillis(value.getDateValue()); 583 584 assertWithMessage("Wrong hour on AutofillValue %s", value) 585 .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour); 586 assertWithMessage("Wrong minute on AutofillValue %s", value) 587 .that(cal.get(Calendar.MINUTE)).isEqualTo(minute); 588 } 589 590 /** 591 * Asserts the auto-fill value of a time-based node. 592 */ assertTimeValue(ViewNode node, int hour, int minute)593 static void assertTimeValue(ViewNode node, int hour, int minute) { 594 assertTimeValue(node, node.getAutofillValue(), hour, minute); 595 } 596 597 /** 598 * Asserts the auto-fill value of a time-based view. 599 */ assertTimeValue(View view, int hour, int minute)600 static void assertTimeValue(View view, int hour, int minute) { 601 assertTimeValue(view, view.getAutofillValue(), hour, minute); 602 } 603 604 /** 605 * Asserts a text-based node exists and is sanitized. 606 */ assertTextIsSanitized(AssistStructure structure, String resourceId)607 static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) { 608 final ViewNode node = findNodeByResourceId(structure, resourceId); 609 assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull(); 610 assertTextIsSanitized(node); 611 return node; 612 } 613 614 /** 615 * Asserts a list-based node exists and is sanitized. 616 */ assertListValueIsSanitized(AssistStructure structure, String resourceId)617 static void assertListValueIsSanitized(AssistStructure structure, String resourceId) { 618 final ViewNode node = findNodeByResourceId(structure, resourceId); 619 assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull(); 620 assertTextIsSanitized(node); 621 } 622 623 /** 624 * Asserts a toggle node exists and is sanitized. 625 */ assertToggleIsSanitized(AssistStructure structure, String resourceId)626 static void assertToggleIsSanitized(AssistStructure structure, String resourceId) { 627 final ViewNode node = findNodeByResourceId(structure, resourceId); 628 assertNodeHasNoAutofillValue(node); 629 assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked()) 630 .isFalse(); 631 } 632 633 /** 634 * Asserts a node exists and has the {@code expected} number of children. 635 */ assertNumberOfChildren(AssistStructure structure, String resourceId, int expected)636 static void assertNumberOfChildren(AssistStructure structure, String resourceId, int expected) { 637 final ViewNode node = findNodeByResourceId(structure, resourceId); 638 final int actual = node.getChildCount(); 639 if (actual != expected) { 640 dumpStructure("assertNumberOfChildren()", structure); 641 throw new AssertionError("assertNumberOfChildren() for " + resourceId 642 + " failed: expected " + expected + ", got " + actual); 643 } 644 } 645 646 /** 647 * Asserts the number of children in the Assist structure. 648 */ assertNumberOfChildren(AssistStructure structure, int expected)649 static void assertNumberOfChildren(AssistStructure structure, int expected) { 650 assertWithMessage("wrong number of nodes").that(structure.getWindowNodeCount()) 651 .isEqualTo(1); 652 final int actual = getNumberNodes(structure); 653 if (actual != expected) { 654 dumpStructure("assertNumberOfChildren()", structure); 655 throw new AssertionError("assertNumberOfChildren() for structure failed: expected " 656 + expected + ", got " + actual); 657 } 658 } 659 660 /** 661 * Gets the total number of nodes in an structure. 662 */ getNumberNodes(AssistStructure structure)663 static int getNumberNodes(AssistStructure structure) { 664 int count = 0; 665 final int nodes = structure.getWindowNodeCount(); 666 for (int i = 0; i < nodes; i++) { 667 final WindowNode windowNode = structure.getWindowNodeAt(i); 668 final ViewNode rootNode = windowNode.getRootViewNode(); 669 count += getNumberNodes(rootNode); 670 } 671 return count; 672 } 673 674 /** 675 * Gets the total number of nodes in an node, including all descendants and the node itself. 676 */ getNumberNodes(ViewNode node)677 static int getNumberNodes(ViewNode node) { 678 int count = 1; 679 final int childrenSize = node.getChildCount(); 680 if (childrenSize > 0) { 681 for (int i = 0; i < childrenSize; i++) { 682 count += getNumberNodes(node.getChildAt(i)); 683 } 684 } 685 return count; 686 } 687 688 /** 689 * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given 690 * {@code resourceIds}. 691 */ getAutofillIds(Function<String, ViewNode> nodeResolver, String[] resourceIds)692 static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver, 693 String[] resourceIds) { 694 if (resourceIds == null) return null; 695 696 final AutofillId[] requiredIds = new AutofillId[resourceIds.length]; 697 for (int i = 0; i < resourceIds.length; i++) { 698 final String resourceId = resourceIds[i]; 699 final ViewNode node = nodeResolver.apply(resourceId); 700 if (node == null) { 701 throw new AssertionError("No node with savable resourceId " + resourceId); 702 } 703 requiredIds[i] = node.getAutofillId(); 704 705 } 706 return requiredIds; 707 } 708 709 /** 710 * Prevents the screen to rotate by itself 711 */ disableAutoRotation(UiBot uiBot)712 public static void disableAutoRotation(UiBot uiBot) throws Exception { 713 runShellCommand(ACCELLEROMETER_CHANGE, 0); 714 uiBot.setScreenOrientation(PORTRAIT); 715 } 716 717 /** 718 * Allows the screen to rotate by itself 719 */ allowAutoRotation()720 public static void allowAutoRotation() { 721 runShellCommand(ACCELLEROMETER_CHANGE, 1); 722 } 723 724 /** 725 * Gets the maximum number of partitions per session. 726 */ getMaxPartitions()727 public static int getMaxPartitions() { 728 return Integer.parseInt(runShellCommand("cmd autofill get max_partitions")); 729 } 730 731 /** 732 * Sets the maximum number of partitions per session. 733 */ setMaxPartitions(int value)734 public static void setMaxPartitions(int value) { 735 runShellCommand("cmd autofill set max_partitions %d", value); 736 assertThat(getMaxPartitions()).isEqualTo(value); 737 } 738 739 /** 740 * Checks if device supports the Autofill feature. 741 */ hasAutofillFeature()742 public static boolean hasAutofillFeature() { 743 return RequiredFeatureRule.hasFeature(PackageManager.FEATURE_AUTOFILL); 744 } 745 746 /** 747 * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi. 748 */ isAutofillWindowFullScreen(Context context)749 public static boolean isAutofillWindowFullScreen(Context context) { 750 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); 751 } 752 753 /** 754 * Checks if screen orientation can be changed. 755 */ isRotationSupported(Context context)756 public static boolean isRotationSupported(Context context) { 757 return !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); 758 } 759 760 /** 761 * Uses Shell command to get the Autofill logging level. 762 */ getLoggingLevel()763 public static String getLoggingLevel() { 764 return runShellCommand("cmd autofill get log_level"); 765 } 766 767 /** 768 * Uses Shell command to set the Autofill logging level. 769 */ setLoggingLevel(String level)770 public static void setLoggingLevel(String level) { 771 runShellCommand("cmd autofill set log_level %s", level); 772 } 773 774 /** 775 * Uses Settings to enable the given autofill service for the default user, and checks the 776 * value was properly check, throwing an exception if it was not. 777 */ enableAutofillService(@onNull Context context, @NonNull String serviceName)778 public static void enableAutofillService(@NonNull Context context, 779 @NonNull String serviceName) { 780 if (isAutofillServiceEnabled(serviceName)) return; 781 782 SettingsHelper.syncSet(context, AUTOFILL_SERVICE, serviceName); 783 } 784 785 /** 786 * Uses Settings to disable the given autofill service for the default user, and checks the 787 * value was properly check, throwing an exception if it was not. 788 */ disableAutofillService(@onNull Context context, @NonNull String serviceName)789 public static void disableAutofillService(@NonNull Context context, 790 @NonNull String serviceName) { 791 if (!isAutofillServiceEnabled(serviceName)) return; 792 793 SettingsHelper.syncDelete(context, AUTOFILL_SERVICE); 794 } 795 796 /** 797 * Checks whether the given service is set as the autofill service for the default user. 798 */ isAutofillServiceEnabled(@onNull String serviceName)799 private static boolean isAutofillServiceEnabled(@NonNull String serviceName) { 800 final String actualName = SettingsHelper.get(AUTOFILL_SERVICE); 801 return serviceName.equals(actualName); 802 } 803 804 /** 805 * Asserts whether the given service is enabled as the autofill service for the default user. 806 */ assertAutofillServiceStatus(@onNull String serviceName, boolean enabled)807 public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) { 808 final String actual = SettingsHelper.get(AUTOFILL_SERVICE); 809 final String expected = enabled ? serviceName : "null"; 810 assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE) 811 .that(actual).isEqualTo(expected); 812 } 813 814 /** 815 * Asserts that there is a pending session for the given package. 816 */ assertHasSessions(String packageName)817 public static void assertHasSessions(String packageName) { 818 final String result = runShellCommand(CMD_LIST_SESSIONS); 819 assertThat(result).contains(packageName); 820 } 821 822 /** 823 * Gets the instrumentation context. 824 */ getContext()825 public static Context getContext() { 826 return InstrumentationRegistry.getInstrumentation().getContext(); 827 } 828 829 /** 830 * Cleans up the autofill state; should be called before pretty much any test. 831 */ preTestCleanup()832 public static void preTestCleanup() { 833 if (!hasAutofillFeature()) return; 834 835 Log.d(TAG, "preTestCleanup()"); 836 837 disableAutofillService(getContext(), SERVICE_NAME); 838 InstrumentedAutoFillService.setIgnoreUnexpectedRequests(true); 839 840 InstrumentedAutoFillService.resetStaticState(); 841 AuthenticationActivity.resetStaticState(); 842 } 843 844 /** 845 * Asserts the node has an {@code HTMLInfo} property, with the given tag. 846 */ assertHasHtmlTag(ViewNode node, String expectedTag)847 public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) { 848 final HtmlInfo info = node.getHtmlInfo(); 849 assertWithMessage("node doesn't have htmlInfo").that(info).isNotNull(); 850 assertWithMessage("wrong tag").that(info.getTag()).isEqualTo(expectedTag); 851 return info; 852 } 853 854 /** 855 * Gets the value of an {@code HTMLInfo} attribute. 856 */ 857 @Nullable getAttributeValue(HtmlInfo info, String attribute)858 public static String getAttributeValue(HtmlInfo info, String attribute) { 859 for (Pair<String, String> pair : info.getAttributes()) { 860 if (pair.first.equals(attribute)) { 861 return pair.second; 862 } 863 } 864 return null; 865 } 866 867 /** 868 * Asserts a {@code HTMLInfo} has an attribute with a given value. 869 */ assertHasAttribute(HtmlInfo info, String attribute, String expectedValue)870 public static void assertHasAttribute(HtmlInfo info, String attribute, String expectedValue) { 871 final String actualValue = getAttributeValue(info, attribute); 872 assertWithMessage("Attribute %s not found", attribute).that(actualValue).isNotNull(); 873 assertWithMessage("Wrong value for Attribute %s", attribute) 874 .that(actualValue).isEqualTo(expectedValue); 875 } 876 877 /** 878 * Finds a {@link WebView} node given its expected form name. 879 */ findWebViewNodeByFormName(AssistStructure structure, String formName)880 public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) { 881 return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER); 882 } 883 assertClientState(Object container, Bundle clientState, String key, String value)884 private static void assertClientState(Object container, Bundle clientState, 885 String key, String value) { 886 assertWithMessage("'%s' should have client state", container) 887 .that(clientState).isNotNull(); 888 assertWithMessage("Wrong number of client state extras on '%s'", container) 889 .that(clientState.keySet().size()).isEqualTo(1); 890 assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container) 891 .that(clientState.getString(key)).isEqualTo(value); 892 } 893 894 /** 895 * Asserts the content of a {@link FillEventHistory#getClientState()}. 896 * 897 * @param history event to be asserted 898 * @param key the only key expected in the client state bundle 899 * @param value the only value expected in the client state bundle 900 */ 901 @SuppressWarnings("javadoc") assertDeprecatedClientState(@onNull FillEventHistory history, @NonNull String key, @NonNull String value)902 public static void assertDeprecatedClientState(@NonNull FillEventHistory history, 903 @NonNull String key, @NonNull String value) { 904 assertThat(history).isNotNull(); 905 @SuppressWarnings("deprecation") 906 final Bundle clientState = history.getClientState(); 907 assertClientState(history, clientState, key, value); 908 } 909 910 /** 911 * Asserts the {@link FillEventHistory#getClientState()} is not set. 912 * 913 * @param history event to be asserted 914 */ 915 @SuppressWarnings("javadoc") assertNoDeprecatedClientState(@onNull FillEventHistory history)916 public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) { 917 assertThat(history).isNotNull(); 918 @SuppressWarnings("deprecation") 919 final Bundle clientState = history.getClientState(); 920 assertWithMessage("History '%s' should not have client state", history) 921 .that(clientState).isNull(); 922 } 923 924 /** 925 * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}. 926 * 927 * @param event event to be asserted 928 * @param eventType expected type 929 * @param datasetId dataset set id expected in the event 930 * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't 931 * have client state) 932 * @param value the only value expected in the client state bundle (or {@code null} if it 933 * shouldn't have client state) 934 * @param fieldClassificationResults expected results when asserting field classification 935 */ assertFillEvent(@onNull FillEventHistory.Event event, int eventType, @Nullable String datasetId, @Nullable String key, @Nullable String value, @Nullable FieldClassificationResult[] fieldClassificationResults)936 private static void assertFillEvent(@NonNull FillEventHistory.Event event, 937 int eventType, @Nullable String datasetId, 938 @Nullable String key, @Nullable String value, 939 @Nullable FieldClassificationResult[] fieldClassificationResults) { 940 assertThat(event).isNotNull(); 941 assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType); 942 if (datasetId == null) { 943 assertWithMessage("Event %s should not have dataset id", event) 944 .that(event.getDatasetId()).isNull(); 945 } else { 946 assertWithMessage("Wrong dataset id for %s", event) 947 .that(event.getDatasetId()).isEqualTo(datasetId); 948 } 949 final Bundle clientState = event.getClientState(); 950 if (key == null) { 951 assertWithMessage("Event '%s' should not have client state", event) 952 .that(clientState).isNull(); 953 } else { 954 assertClientState(event, clientState, key, value); 955 } 956 assertWithMessage("Event '%s' should not have selected datasets", event) 957 .that(event.getSelectedDatasetIds()).isEmpty(); 958 assertWithMessage("Event '%s' should not have ignored datasets", event) 959 .that(event.getIgnoredDatasetIds()).isEmpty(); 960 assertWithMessage("Event '%s' should not have changed fields", event) 961 .that(event.getChangedFields()).isEmpty(); 962 assertWithMessage("Event '%s' should not have manually-entered fields", event) 963 .that(event.getManuallyEnteredField()).isEmpty(); 964 final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification(); 965 if (fieldClassificationResults == null) { 966 assertThat(detectedFields).isEmpty(); 967 } else { 968 assertThat(detectedFields).hasSize(fieldClassificationResults.length); 969 int i = 0; 970 for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) { 971 assertMatches(i, entry, fieldClassificationResults[i]); 972 i++; 973 } 974 } 975 } 976 assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult, FieldClassificationResult expectedResult)977 private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult, 978 FieldClassificationResult expectedResult) { 979 assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey()) 980 .isEqualTo(expectedResult.id); 981 final List<Match> matches = actualResult.getValue().getMatches(); 982 assertWithMessage("Wrong number of matches: " + matches).that(matches.size()) 983 .isEqualTo(expectedResult.remoteIds.length); 984 for (int j = 0; j < matches.size(); j++) { 985 final Match match = matches.get(j); 986 assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match) 987 .that(match.getCategoryId()).isEqualTo(expectedResult.remoteIds[j]); 988 assertWithMessage("Wrong score at (%s, %s): %s", i, j, match) 989 .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]); 990 } 991 } 992 993 /** 994 * Asserts the content of a 995 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event. 996 * 997 * @param event event to be asserted 998 * @param datasetId dataset set id expected in the event 999 */ assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId)1000 public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event, 1001 @Nullable String datasetId) { 1002 assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null); 1003 } 1004 1005 /** 1006 * Asserts the content of a 1007 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event. 1008 * 1009 * @param event event to be asserted 1010 * @param datasetId dataset set id expected in the event 1011 * @param key the only key expected in the client state bundle 1012 * @param value the only value expected in the client state bundle 1013 */ assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId, @Nullable String key, @Nullable String value)1014 public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event, 1015 @Nullable String datasetId, @Nullable String key, @Nullable String value) { 1016 assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null); 1017 } 1018 1019 /** 1020 * Asserts the content of a 1021 * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event. 1022 * 1023 * @param event event to be asserted 1024 * @param datasetId dataset set id expected in the event 1025 * @param key the only key expected in the client state bundle 1026 * @param value the only value expected in the client state bundle 1027 */ assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @NonNull String datasetId, @NonNull String key, @NonNull String value)1028 public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event, 1029 @NonNull String datasetId, @NonNull String key, @NonNull String value) { 1030 assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null); 1031 } 1032 1033 /** 1034 * Asserts the content of a 1035 * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event. 1036 * 1037 * @param event event to be asserted 1038 * @param datasetId dataset set id expected in the event 1039 */ assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @NonNull String datasetId)1040 public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event, 1041 @NonNull String datasetId) { 1042 assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null); 1043 } 1044 1045 /** 1046 * Asserts the content of a 1047 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED} 1048 * event. 1049 * 1050 * @param event event to be asserted 1051 * @param datasetId dataset set id expected in the event 1052 * @param key the only key expected in the client state bundle 1053 * @param value the only value expected in the client state bundle 1054 */ assertFillEventForDatasetAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1055 public static void assertFillEventForDatasetAuthenticationSelected( 1056 @NonNull FillEventHistory.Event event, 1057 @Nullable String datasetId, @NonNull String key, @NonNull String value) { 1058 assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null); 1059 } 1060 1061 /** 1062 * Asserts the content of a 1063 * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event. 1064 * 1065 * @param event event to be asserted 1066 * @param datasetId dataset set id expected in the event 1067 * @param key the only key expected in the client state bundle 1068 * @param value the only value expected in the client state bundle 1069 */ assertFillEventForAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1070 public static void assertFillEventForAuthenticationSelected( 1071 @NonNull FillEventHistory.Event event, 1072 @Nullable String datasetId, @NonNull String key, @NonNull String value) { 1073 assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null); 1074 } 1075 assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull AutofillId fieldId, @NonNull String remoteId, float score)1076 public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event, 1077 @NonNull AutofillId fieldId, @NonNull String remoteId, float score) { 1078 assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, 1079 new FieldClassificationResult[] { 1080 new FieldClassificationResult(fieldId, remoteId, score) 1081 }); 1082 } 1083 assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull FieldClassificationResult[] results)1084 public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event, 1085 @NonNull FieldClassificationResult[] results) { 1086 assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results); 1087 } 1088 assertFillEventForContextCommitted(@onNull FillEventHistory.Event event)1089 public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) { 1090 assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null); 1091 } 1092 1093 @NonNull getActivityName(List<FillContext> contexts)1094 public static String getActivityName(List<FillContext> contexts) { 1095 if (contexts == null) return "N/A (null contexts)"; 1096 1097 if (contexts.isEmpty()) return "N/A (empty contexts)"; 1098 1099 final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure(); 1100 if (structure == null) return "N/A (no AssistStructure)"; 1101 1102 final ComponentName componentName = structure.getActivityComponent(); 1103 if (componentName == null) return "N/A (no component name)"; 1104 1105 return componentName.flattenToShortString(); 1106 } 1107 assertFloat(float actualValue, float expectedValue)1108 public static void assertFloat(float actualValue, float expectedValue) { 1109 assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue); 1110 } 1111 assertHasFlags(int actualFlags, int expectedFlags)1112 public static void assertHasFlags(int actualFlags, int expectedFlags) { 1113 assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags) 1114 .that(actualFlags & expectedFlags).isEqualTo(expectedFlags); 1115 } 1116 callbackEventAsString(int event)1117 public static String callbackEventAsString(int event) { 1118 switch (event) { 1119 case AutofillCallback.EVENT_INPUT_HIDDEN: 1120 return "HIDDEN"; 1121 case AutofillCallback.EVENT_INPUT_SHOWN: 1122 return "SHOWN"; 1123 case AutofillCallback.EVENT_INPUT_UNAVAILABLE: 1124 return "UNAVAILABLE"; 1125 default: 1126 return "UNKNOWN:" + event; 1127 } 1128 } 1129 importantForAutofillAsString(int mode)1130 public static String importantForAutofillAsString(int mode) { 1131 switch (mode) { 1132 case View.IMPORTANT_FOR_AUTOFILL_AUTO: 1133 return "IMPORTANT_FOR_AUTOFILL_AUTO"; 1134 case View.IMPORTANT_FOR_AUTOFILL_YES: 1135 return "IMPORTANT_FOR_AUTOFILL_YES"; 1136 case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS: 1137 return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS"; 1138 case View.IMPORTANT_FOR_AUTOFILL_NO: 1139 return "IMPORTANT_FOR_AUTOFILL_NO"; 1140 case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS: 1141 return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS"; 1142 default: 1143 return "UNKNOWN:" + mode; 1144 } 1145 } 1146 hasHint(@ullable String[] hints, @Nullable Object expectedHint)1147 public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) { 1148 if (hints == null || expectedHint == null) return false; 1149 for (String actualHint : hints) { 1150 if (expectedHint.equals(actualHint)) return true; 1151 } 1152 return false; 1153 } 1154 1155 /** 1156 * Asserts that 2 bitmaps have are the same. If they aren't throws an exception and dump them 1157 * locally so their can be visually inspected. 1158 * 1159 * @param filename base name of the files generated in case of error 1160 * @param bitmap1 first bitmap to be compared 1161 * @param bitmap2 second bitmap to be compared 1162 */ assertBitmapsAreSame(@onNull String filename, @Nullable Bitmap bitmap1, @Nullable Bitmap bitmap2)1163 public static void assertBitmapsAreSame(@NonNull String filename, @Nullable Bitmap bitmap1, 1164 @Nullable Bitmap bitmap2) throws IOException { 1165 assertWithMessage("1st bitmap is null").that(bitmap1).isNotNull(); 1166 assertWithMessage("2nd bitmap is null").that(bitmap2).isNotNull(); 1167 final boolean same = bitmap1.sameAs(bitmap2); 1168 if (same) { 1169 Log.v(TAG, "bitmap comparison passed for " + filename); 1170 return; 1171 } 1172 1173 final File dir = new File(LOCAL_DIRECTORY); 1174 dir.mkdirs(); 1175 if (!dir.exists()) { 1176 Log.e(TAG, "Could not create directory " + dir); 1177 throw new AssertionError("bitmap comparison failed for " + filename 1178 + ", and bitmaps could not be dumped on " + dir); 1179 } 1180 final File dump1 = dumpBitmap(bitmap1, dir, filename + "-1.png"); 1181 final File dump2 = dumpBitmap(bitmap2, dir, filename + "-2.png"); 1182 throw new AssertionError( 1183 "bitmap comparison failed; check contents of " + dump1 + " and " + dump2); 1184 } 1185 1186 @Nullable dumpBitmap(@onNull Bitmap bitmap, @NonNull File dir, @NonNull String filename)1187 private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir, 1188 @NonNull String filename) throws IOException { 1189 final File file = new File(dir, filename); 1190 if (file.exists()) { 1191 file.delete(); 1192 } 1193 if (!file.createNewFile()) { 1194 Log.e(TAG, "Could not create file " + file); 1195 return null; 1196 } 1197 Log.d(TAG, "Dumping bitmap at " + file); 1198 BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName()); 1199 return file; 1200 } 1201 Helper()1202 private Helper() { 1203 throw new UnsupportedOperationException("contain static methods only"); 1204 } 1205 1206 static class FieldClassificationResult { 1207 public final AutofillId id; 1208 public final String[] remoteIds; 1209 public final float[] scores; 1210 FieldClassificationResult(@onNull AutofillId id, @NonNull String remoteId, float score)1211 FieldClassificationResult(@NonNull AutofillId id, @NonNull String remoteId, float score) { 1212 this(id, new String[] { remoteId }, new float[] { score }); 1213 } 1214 FieldClassificationResult(@onNull AutofillId id, @NonNull String[] remoteIds, float[] scores)1215 FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] remoteIds, 1216 float[] scores) { 1217 this.id = id; 1218 this.remoteIds = remoteIds; 1219 this.scores = scores; 1220 } 1221 } 1222 } 1223