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.testcore; 18 19 import static android.autofillservice.cts.testcore.UiBot.PORTRAIT; 20 import static android.provider.Settings.Secure.AUTOFILL_SERVICE; 21 import static android.provider.Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE; 22 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; 23 import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED; 24 import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED; 25 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASETS_SHOWN; 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.android.compatibility.common.util.ShellUtils.runShellCommand; 31 32 import static com.google.common.truth.Truth.assertThat; 33 import static com.google.common.truth.Truth.assertWithMessage; 34 35 import android.app.Activity; 36 import android.app.Instrumentation; 37 import android.app.PendingIntent; 38 import android.app.assist.AssistStructure; 39 import android.app.assist.AssistStructure.ViewNode; 40 import android.app.assist.AssistStructure.WindowNode; 41 import android.autofillservice.cts.R; 42 import android.content.AutofillOptions; 43 import android.content.ComponentName; 44 import android.content.ContentResolver; 45 import android.content.Context; 46 import android.content.Intent; 47 import android.content.pm.PackageManager; 48 import android.content.res.Resources; 49 import android.graphics.Bitmap; 50 import android.icu.util.Calendar; 51 import android.os.Bundle; 52 import android.os.Environment; 53 import android.provider.Settings; 54 import android.service.autofill.FieldClassification; 55 import android.service.autofill.FieldClassification.Match; 56 import android.service.autofill.FillContext; 57 import android.service.autofill.FillEventHistory; 58 import android.service.autofill.InlinePresentation; 59 import android.text.TextUtils; 60 import android.util.Log; 61 import android.util.Pair; 62 import android.util.Size; 63 import android.view.View; 64 import android.view.ViewGroup; 65 import android.view.ViewStructure.HtmlInfo; 66 import android.view.autofill.AutofillId; 67 import android.view.autofill.AutofillManager; 68 import android.view.autofill.AutofillManager.AutofillCallback; 69 import android.view.autofill.AutofillValue; 70 import android.webkit.WebView; 71 import android.widget.RemoteViews; 72 import android.widget.inline.InlinePresentationSpec; 73 74 import androidx.annotation.NonNull; 75 import androidx.annotation.Nullable; 76 import androidx.autofill.inline.v1.InlineSuggestionUi; 77 import androidx.test.platform.app.InstrumentationRegistry; 78 79 import com.android.compatibility.common.util.BitmapUtils; 80 import com.android.compatibility.common.util.OneTimeSettingsListener; 81 import com.android.compatibility.common.util.SettingsUtils; 82 import com.android.compatibility.common.util.ShellUtils; 83 import com.android.compatibility.common.util.TestNameUtils; 84 import com.android.compatibility.common.util.Timeout; 85 86 import java.io.File; 87 import java.io.IOException; 88 import java.util.List; 89 import java.util.Map; 90 import java.util.Map.Entry; 91 import java.util.concurrent.BlockingQueue; 92 import java.util.concurrent.TimeUnit; 93 import java.util.function.Function; 94 import java.util.regex.Pattern; 95 96 /** 97 * Helper for common funcionalities. 98 */ 99 public final class Helper { 100 101 public static final String TAG = "AutoFillCtsHelper"; 102 103 public static final boolean VERBOSE = false; 104 105 public static final String MY_PACKAGE = "android.autofillservice.cts"; 106 107 public static final String ID_USERNAME_LABEL = "username_label"; 108 public static final String ID_USERNAME = "username"; 109 public static final String ID_PASSWORD_LABEL = "password_label"; 110 public static final String ID_PASSWORD = "password"; 111 public static final String ID_LOGIN = "login"; 112 public static final String ID_OUTPUT = "output"; 113 public static final String ID_STATIC_TEXT = "static_text"; 114 public static final String ID_EMPTY = "empty"; 115 public static final String ID_CANCEL_FILL = "cancel_fill"; 116 117 public static final String NULL_DATASET_ID = null; 118 119 public static final char LARGE_STRING_CHAR = '6'; 120 // NOTE: cannot be much large as it could ANR and fail the test. 121 public static final int LARGE_STRING_SIZE = 100_000; 122 public static final String LARGE_STRING = com.android.compatibility.common.util.TextUtils 123 .repeat(LARGE_STRING_CHAR, LARGE_STRING_SIZE); 124 125 /** 126 * Can be used in cases where the autofill values is required by irrelevant (like adding a 127 * value to an authenticated dataset). 128 */ 129 public static final String UNUSED_AUTOFILL_VALUE = null; 130 131 private static final String ACCELLEROMETER_CHANGE = 132 "content insert --uri content://settings/system --bind name:s:accelerometer_rotation " 133 + "--bind value:i:%d"; 134 135 private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory() 136 + "/CtsAutoFillServiceTestCases"; 137 138 private static final Timeout SETTINGS_BASED_SHELL_CMD_TIMEOUT = new Timeout( 139 "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2, 140 OneTimeSettingsListener.DEFAULT_TIMEOUT_MS); 141 142 /** 143 * Helper interface used to filter nodes. 144 * 145 * @param <T> node type 146 */ 147 interface NodeFilter<T> { 148 /** 149 * Returns whether the node passes the filter for such given id. 150 */ matches(T node, Object id)151 boolean matches(T node, Object id); 152 } 153 154 private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> { 155 return id.equals(node.getIdEntry()); 156 }; 157 158 private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> { 159 return id.equals(getHtmlName(node)); 160 }; 161 162 private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> { 163 return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry()); 164 }; 165 166 private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> { 167 return id.equals(node.getText()); 168 }; 169 170 private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> { 171 return hasHint(node.getAutofillHints(), id); 172 }; 173 174 private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> { 175 final String className = node.getClassName(); 176 if (!className.equals("android.webkit.WebView")) return false; 177 178 final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form"); 179 final String formName = getAttributeValue(htmlInfo, "name"); 180 return id.equals(formName); 181 }; 182 183 private static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> { 184 return hasHint(view.getAutofillHints(), id); 185 }; 186 toString(AssistStructure structure, StringBuilder builder)187 private static String toString(AssistStructure structure, StringBuilder builder) { 188 builder.append("[component=").append(structure.getActivityComponent()); 189 final int nodes = structure.getWindowNodeCount(); 190 for (int i = 0; i < nodes; i++) { 191 final WindowNode windowNode = structure.getWindowNodeAt(i); 192 dump(builder, windowNode.getRootViewNode(), " ", 0); 193 } 194 return builder.append(']').toString(); 195 } 196 197 @NonNull toString(@onNull AssistStructure structure)198 public static String toString(@NonNull AssistStructure structure) { 199 return toString(structure, new StringBuilder()); 200 } 201 202 @Nullable toString(@ullable AutofillValue value)203 public static String toString(@Nullable AutofillValue value) { 204 if (value == null) return null; 205 if (value.isText()) { 206 // We don't care about PII... 207 final CharSequence text = value.getTextValue(); 208 return text == null ? null : text.toString(); 209 } 210 return value.toString(); 211 } 212 213 /** 214 * Dump the assist structure on logcat. 215 */ dumpStructure(String message, AssistStructure structure)216 public static void dumpStructure(String message, AssistStructure structure) { 217 Log.i(TAG, toString(structure, new StringBuilder(message))); 218 } 219 220 /** 221 * Dump the contexts on logcat. 222 */ dumpStructure(String message, List<FillContext> contexts)223 public static void dumpStructure(String message, List<FillContext> contexts) { 224 for (FillContext context : contexts) { 225 dumpStructure(message, context.getStructure()); 226 } 227 } 228 229 /** 230 * Dumps the state of the autofill service on logcat. 231 */ dumpAutofillService(@onNull String tag)232 public static void dumpAutofillService(@NonNull String tag) { 233 final String autofillDump = runShellCommand("dumpsys autofill"); 234 Log.i(tag, "dumpsys autofill\n\n" + autofillDump); 235 final String myServiceDump = runShellCommand("dumpsys activity service %s", 236 InstrumentedAutoFillService.SERVICE_NAME); 237 Log.i(tag, "my service dump: \n" + myServiceDump); 238 } 239 240 /** 241 * Dumps the state of {@link android.service.autofill.InlineSuggestionRenderService}, and assert 242 * that it says the number of active inline suggestion views is the given number. 243 * 244 * <p>Note that ideally we should have a test api to fetch the number and verify against it. 245 * But at the time this test is added for Android 11, we have passed the deadline for adding 246 * the new test api, hence this approach. 247 */ assertActiveViewCountFromInlineSuggestionRenderService(int count)248 public static void assertActiveViewCountFromInlineSuggestionRenderService(int count) { 249 String response = runShellCommand( 250 "dumpsys activity service .InlineSuggestionRenderService"); 251 Log.d(TAG, "InlineSuggestionRenderService dump: " + response); 252 Pattern pattern = Pattern.compile(".*mActiveInlineSuggestions: " + count + ".*"); 253 assertWithMessage("Expecting view count " + count 254 + ", but seeing different count from service dumpsys " + response).that( 255 pattern.matcher(response).find()).isTrue(); 256 } 257 258 /** 259 * Sets whether the user completed the initial setup. 260 */ setUserComplete(Context context, boolean complete)261 public static void setUserComplete(Context context, boolean complete) { 262 SettingsUtils.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null); 263 } 264 dump(@onNull StringBuilder builder, @NonNull ViewNode node, @NonNull String prefix, int childId)265 private static void dump(@NonNull StringBuilder builder, @NonNull ViewNode node, 266 @NonNull String prefix, int childId) { 267 final int childrenSize = node.getChildCount(); 268 builder.append("\n").append(prefix) 269 .append("child #").append(childId).append(':'); 270 append(builder, "afId", node.getAutofillId()); 271 append(builder, "afType", node.getAutofillType()); 272 append(builder, "afValue", toString(node.getAutofillValue())); 273 append(builder, "resId", node.getIdEntry()); 274 append(builder, "class", node.getClassName()); 275 append(builder, "text", node.getText()); 276 append(builder, "webDomain", node.getWebDomain()); 277 append(builder, "checked", node.isChecked()); 278 append(builder, "focused", node.isFocused()); 279 final HtmlInfo htmlInfo = node.getHtmlInfo(); 280 if (htmlInfo != null) { 281 builder.append(", HtmlInfo[tag=").append(htmlInfo.getTag()) 282 .append(", attrs: ").append(htmlInfo.getAttributes()).append(']'); 283 } 284 if (childrenSize > 0) { 285 append(builder, "#children", childrenSize).append("\n").append(prefix); 286 prefix += " "; 287 if (childrenSize > 0) { 288 for (int i = 0; i < childrenSize; i++) { 289 dump(builder, node.getChildAt(i), prefix, i); 290 } 291 } 292 } 293 } 294 295 /** 296 * Appends a field value to a {@link StringBuilder} when it's not {@code null}. 297 */ 298 @NonNull append(@onNull StringBuilder builder, @NonNull String field, @Nullable Object value)299 public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field, 300 @Nullable Object value) { 301 if (value == null) return builder; 302 303 if ((value instanceof Boolean) && ((Boolean) value)) { 304 return builder.append(", ").append(field); 305 } 306 307 if (value instanceof Integer && ((Integer) value) == 0 308 || value instanceof CharSequence && TextUtils.isEmpty((CharSequence) value)) { 309 return builder; 310 } 311 312 return builder.append(", ").append(field).append('=').append(value); 313 } 314 315 /** 316 * Appends a field value to a {@link StringBuilder} when it's {@code true}. 317 */ 318 @NonNull append(@onNull StringBuilder builder, @NonNull String field, boolean value)319 public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field, 320 boolean value) { 321 if (value) { 322 builder.append(", ").append(field); 323 } 324 return builder; 325 } 326 327 /** 328 * Gets a node if it matches the filter criteria for the given id. 329 */ findNodeByFilter(@onNull AssistStructure structure, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)330 public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id, 331 @NonNull NodeFilter<ViewNode> filter) { 332 Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent()); 333 final int nodes = structure.getWindowNodeCount(); 334 for (int i = 0; i < nodes; i++) { 335 final WindowNode windowNode = structure.getWindowNodeAt(i); 336 final ViewNode rootNode = windowNode.getRootViewNode(); 337 final ViewNode node = findNodeByFilter(rootNode, id, filter); 338 if (node != null) { 339 return node; 340 } 341 } 342 return null; 343 } 344 345 /** 346 * Gets a node if it matches the filter criteria for the given id. 347 */ findNodeByFilter(@onNull List<FillContext> contexts, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)348 public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id, 349 @NonNull NodeFilter<ViewNode> filter) { 350 for (FillContext context : contexts) { 351 ViewNode node = findNodeByFilter(context.getStructure(), id, filter); 352 if (node != null) { 353 return node; 354 } 355 } 356 return null; 357 } 358 359 /** 360 * Gets a node if it matches the filter criteria for the given id. 361 */ findNodeByFilter(@onNull ViewNode node, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)362 public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id, 363 @NonNull NodeFilter<ViewNode> filter) { 364 if (filter.matches(node, id)) { 365 return node; 366 } 367 final int childrenSize = node.getChildCount(); 368 if (childrenSize > 0) { 369 for (int i = 0; i < childrenSize; i++) { 370 final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter); 371 if (found != null) { 372 return found; 373 } 374 } 375 } 376 return null; 377 } 378 379 /** 380 * Gets a node given its Android resource id, or {@code null} if not found. 381 */ findNodeByResourceId(AssistStructure structure, String resourceId)382 public static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) { 383 return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER); 384 } 385 386 /** 387 * Gets a node given its Android resource id, or {@code null} if not found. 388 */ findNodeByResourceId(List<FillContext> contexts, String resourceId)389 public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) { 390 return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER); 391 } 392 393 /** 394 * Gets a node given its Android resource id, or {@code null} if not found. 395 */ findNodeByResourceId(ViewNode node, String resourceId)396 public static ViewNode findNodeByResourceId(ViewNode node, String resourceId) { 397 return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER); 398 } 399 400 /** 401 * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found. 402 */ findNodeByHtmlName(AssistStructure structure, String htmlName)403 public static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) { 404 return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER); 405 } 406 407 /** 408 * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found. 409 */ findNodeByHtmlName(List<FillContext> contexts, String htmlName)410 public static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) { 411 return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER); 412 } 413 414 /** 415 * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found. 416 */ findNodeByHtmlName(ViewNode node, String htmlName)417 public static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) { 418 return findNodeByFilter(node, htmlName, HTML_NAME_FILTER); 419 } 420 421 /** 422 * Gets a node given the value of its (single) autofill hint property, or {@code null} if not 423 * found. 424 */ findNodeByAutofillHint(ViewNode node, String hint)425 public static ViewNode findNodeByAutofillHint(ViewNode node, String hint) { 426 return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER); 427 } 428 429 /** 430 * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if 431 * not found. 432 */ findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id)433 public static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) { 434 return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER); 435 } 436 437 /** 438 * Gets a node given its Android resource id. 439 */ 440 @NonNull findAutofillIdByResourceId(@onNull FillContext context, @NonNull String resourceId)441 public static AutofillId findAutofillIdByResourceId(@NonNull FillContext context, 442 @NonNull String resourceId) { 443 final ViewNode node = findNodeByFilter(context.getStructure(), resourceId, 444 RESOURCE_ID_FILTER); 445 assertWithMessage("No node for resourceId %s", resourceId).that(node).isNotNull(); 446 return node.getAutofillId(); 447 } 448 449 /** 450 * Gets the {@code name} attribute of a node representing an HTML input tag. 451 */ 452 @Nullable getHtmlName(@onNull ViewNode node)453 public static String getHtmlName(@NonNull ViewNode node) { 454 final HtmlInfo htmlInfo = node.getHtmlInfo(); 455 if (htmlInfo == null) { 456 return null; 457 } 458 final String tag = htmlInfo.getTag(); 459 if (!"input".equals(tag)) { 460 Log.w(TAG, "getHtmlName(): invalid tag (" + tag + ") on " + htmlInfo); 461 return null; 462 } 463 for (Pair<String, String> attr : htmlInfo.getAttributes()) { 464 if ("name".equals(attr.first)) { 465 return attr.second; 466 } 467 } 468 Log.w(TAG, "getHtmlName(): no 'name' attribute on " + htmlInfo); 469 return null; 470 } 471 472 /** 473 * Gets a node given its expected text, or {@code null} if not found. 474 */ findNodeByText(AssistStructure structure, String text)475 public static ViewNode findNodeByText(AssistStructure structure, String text) { 476 return findNodeByFilter(structure, text, TEXT_FILTER); 477 } 478 479 /** 480 * Gets a node given its expected text, or {@code null} if not found. 481 */ findNodeByText(ViewNode node, String text)482 public static ViewNode findNodeByText(ViewNode node, String text) { 483 return findNodeByFilter(node, text, TEXT_FILTER); 484 } 485 486 /** 487 * Gets a view that contains the an autofill hint, or {@code null} if not found. 488 */ findViewByAutofillHint(Activity activity, String hint)489 public static View findViewByAutofillHint(Activity activity, String hint) { 490 final View rootView = activity.getWindow().getDecorView().getRootView(); 491 return findViewByAutofillHint(rootView, hint); 492 } 493 494 /** 495 * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if 496 * not found. 497 */ findViewByAutofillHint(View view, String hint)498 public static View findViewByAutofillHint(View view, String hint) { 499 if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view; 500 if ((view instanceof ViewGroup)) { 501 final ViewGroup group = (ViewGroup) view; 502 for (int i = 0; i < group.getChildCount(); i++) { 503 final View child = findViewByAutofillHint(group.getChildAt(i), hint); 504 if (child != null) return child; 505 } 506 } 507 return null; 508 } 509 510 /** 511 * Asserts a text-based node is sanitized. 512 */ assertTextIsSanitized(ViewNode node)513 public static void assertTextIsSanitized(ViewNode node) { 514 final CharSequence text = node.getText(); 515 final String resourceId = node.getIdEntry(); 516 if (!TextUtils.isEmpty(text)) { 517 throw new AssertionError("text on sanitized field " + resourceId + ": " + text); 518 } 519 520 assertNotFromResources(node); 521 assertNodeHasNoAutofillValue(node); 522 } 523 assertNotFromResources(ViewNode node)524 private static void assertNotFromResources(ViewNode node) { 525 assertThat(node.getTextIdEntry()).isNull(); 526 } 527 assertNodeHasNoAutofillValue(ViewNode node)528 public static void assertNodeHasNoAutofillValue(ViewNode node) { 529 final AutofillValue value = node.getAutofillValue(); 530 if (value != null) { 531 final String text = value.isText() ? value.getTextValue().toString() : "N/A"; 532 throw new AssertionError("node has value: " + value + " text=" + text); 533 } 534 } 535 536 /** 537 * Asserts the contents of a text-based node that is also auto-fillable. 538 */ assertTextOnly(ViewNode node, String expectedValue)539 public static void assertTextOnly(ViewNode node, String expectedValue) { 540 assertText(node, expectedValue, false); 541 assertNotFromResources(node); 542 } 543 544 /** 545 * Asserts the contents of a text-based node that is also auto-fillable. 546 */ assertTextOnly(AssistStructure structure, String resourceId, String expectedValue)547 public static void assertTextOnly(AssistStructure structure, String resourceId, 548 String expectedValue) { 549 final ViewNode node = findNodeByResourceId(structure, resourceId); 550 assertText(node, expectedValue, false); 551 assertNotFromResources(node); 552 } 553 554 /** 555 * Asserts the contents of a text-based node that is also auto-fillable. 556 */ assertTextAndValue(ViewNode node, String expectedValue)557 public static void assertTextAndValue(ViewNode node, String expectedValue) { 558 assertText(node, expectedValue, true); 559 assertNotFromResources(node); 560 } 561 562 /** 563 * Asserts a text-based node exists and verify its values. 564 */ assertTextAndValue(AssistStructure structure, String resourceId, String expectedValue)565 public static ViewNode assertTextAndValue(AssistStructure structure, String resourceId, 566 String expectedValue) { 567 final ViewNode node = findNodeByResourceId(structure, resourceId); 568 assertTextAndValue(node, expectedValue); 569 return node; 570 } 571 572 /** 573 * Asserts a text-based node exists and is sanitized. 574 */ assertValue(AssistStructure structure, String resourceId, String expectedValue)575 public static ViewNode assertValue(AssistStructure structure, String resourceId, 576 String expectedValue) { 577 final ViewNode node = findNodeByResourceId(structure, resourceId); 578 assertTextValue(node, expectedValue); 579 return node; 580 } 581 582 /** 583 * Asserts the values of a text-based node whose string come from resoruces. 584 */ assertTextFromResources(AssistStructure structure, String resourceId, String expectedValue, boolean isAutofillable, String expectedTextIdEntry)585 public static ViewNode assertTextFromResources(AssistStructure structure, String resourceId, 586 String expectedValue, boolean isAutofillable, String expectedTextIdEntry) { 587 final ViewNode node = findNodeByResourceId(structure, resourceId); 588 assertText(node, expectedValue, isAutofillable); 589 assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry); 590 return node; 591 } 592 assertHintFromResources(AssistStructure structure, String resourceId, String expectedValue, String expectedHintIdEntry)593 public static ViewNode assertHintFromResources(AssistStructure structure, String resourceId, 594 String expectedValue, String expectedHintIdEntry) { 595 final ViewNode node = findNodeByResourceId(structure, resourceId); 596 assertThat(node.getHint()).isEqualTo(expectedValue); 597 assertThat(node.getHintIdEntry()).isEqualTo(expectedHintIdEntry); 598 return node; 599 } 600 assertText(ViewNode node, String expectedValue, boolean isAutofillable)601 private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) { 602 assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString()) 603 .isEqualTo(expectedValue); 604 final AutofillValue value = node.getAutofillValue(); 605 final AutofillId id = node.getAutofillId(); 606 if (isAutofillable) { 607 assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull(); 608 assertWithMessage("wrong auto-fill value on %s", id) 609 .that(value.getTextValue().toString()).isEqualTo(expectedValue); 610 } else { 611 assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull(); 612 } 613 } 614 615 /** 616 * Asserts the auto-fill value of a text-based node. 617 */ assertTextValue(ViewNode node, String expectedText)618 public static ViewNode assertTextValue(ViewNode node, String expectedText) { 619 final AutofillValue value = node.getAutofillValue(); 620 final AutofillId id = node.getAutofillId(); 621 assertWithMessage("null autofill value on %s", id).that(value).isNotNull(); 622 assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue(); 623 assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString()) 624 .isEqualTo(expectedText); 625 return node; 626 } 627 628 /** 629 * Asserts the auto-fill value of a list-based node. 630 */ assertListValue(ViewNode node, int expectedIndex)631 public static ViewNode assertListValue(ViewNode node, int expectedIndex) { 632 final AutofillValue value = node.getAutofillValue(); 633 final AutofillId id = node.getAutofillId(); 634 assertWithMessage("null autofill value on %s", id).that(value).isNotNull(); 635 assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue(); 636 assertWithMessage("wrong autofill value on %s", id).that(value.getListValue()) 637 .isEqualTo(expectedIndex); 638 return node; 639 } 640 641 /** 642 * Asserts the auto-fill value of a toggle-based node. 643 */ assertToggleValue(ViewNode node, boolean expectedToggle)644 public static void assertToggleValue(ViewNode node, boolean expectedToggle) { 645 final AutofillValue value = node.getAutofillValue(); 646 final AutofillId id = node.getAutofillId(); 647 assertWithMessage("null autofill value on %s", id).that(value).isNotNull(); 648 assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue(); 649 assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue()) 650 .isEqualTo(expectedToggle); 651 } 652 653 /** 654 * Asserts the auto-fill value of a date-based node. 655 */ assertDateValue(Object object, AutofillValue value, int year, int month, int day)656 public static void assertDateValue(Object object, AutofillValue value, int year, int month, 657 int day) { 658 assertWithMessage("null autofill value on %s", object).that(value).isNotNull(); 659 assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue(); 660 661 final Calendar cal = Calendar.getInstance(); 662 cal.setTimeInMillis(value.getDateValue()); 663 664 assertWithMessage("Wrong year on AutofillValue %s", value) 665 .that(cal.get(Calendar.YEAR)).isEqualTo(year); 666 assertWithMessage("Wrong month on AutofillValue %s", value) 667 .that(cal.get(Calendar.MONTH)).isEqualTo(month); 668 assertWithMessage("Wrong day on AutofillValue %s", value) 669 .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day); 670 } 671 672 /** 673 * Asserts the auto-fill value of a date-based node. 674 */ assertDateValue(ViewNode node, int year, int month, int day)675 public static void assertDateValue(ViewNode node, int year, int month, int day) { 676 assertDateValue(node, node.getAutofillValue(), year, month, day); 677 } 678 679 /** 680 * Asserts the auto-fill value of a date-based view. 681 */ assertDateValue(View view, int year, int month, int day)682 public static void assertDateValue(View view, int year, int month, int day) { 683 assertDateValue(view, view.getAutofillValue(), year, month, day); 684 } 685 686 /** 687 * Asserts the auto-fill value of a time-based node. 688 */ assertTimeValue(Object object, AutofillValue value, int hour, int minute)689 private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) { 690 assertWithMessage("null autofill value on %s", object).that(value).isNotNull(); 691 assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue(); 692 693 final Calendar cal = Calendar.getInstance(); 694 cal.setTimeInMillis(value.getDateValue()); 695 696 assertWithMessage("Wrong hour on AutofillValue %s", value) 697 .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour); 698 assertWithMessage("Wrong minute on AutofillValue %s", value) 699 .that(cal.get(Calendar.MINUTE)).isEqualTo(minute); 700 } 701 702 /** 703 * Asserts the auto-fill value of a time-based node. 704 */ assertTimeValue(ViewNode node, int hour, int minute)705 public static void assertTimeValue(ViewNode node, int hour, int minute) { 706 assertTimeValue(node, node.getAutofillValue(), hour, minute); 707 } 708 709 /** 710 * Asserts the auto-fill value of a time-based view. 711 */ assertTimeValue(View view, int hour, int minute)712 public static void assertTimeValue(View view, int hour, int minute) { 713 assertTimeValue(view, view.getAutofillValue(), hour, minute); 714 } 715 716 /** 717 * Asserts a text-based node exists and is sanitized. 718 */ assertTextIsSanitized(AssistStructure structure, String resourceId)719 public static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) { 720 final ViewNode node = findNodeByResourceId(structure, resourceId); 721 assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull(); 722 assertTextIsSanitized(node); 723 return node; 724 } 725 726 /** 727 * Asserts a list-based node exists and is sanitized. 728 */ assertListValueIsSanitized(AssistStructure structure, String resourceId)729 public static void assertListValueIsSanitized(AssistStructure structure, String resourceId) { 730 final ViewNode node = findNodeByResourceId(structure, resourceId); 731 assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull(); 732 assertTextIsSanitized(node); 733 } 734 735 /** 736 * Asserts a toggle node exists and is sanitized. 737 */ assertToggleIsSanitized(AssistStructure structure, String resourceId)738 public static void assertToggleIsSanitized(AssistStructure structure, String resourceId) { 739 final ViewNode node = findNodeByResourceId(structure, resourceId); 740 assertNodeHasNoAutofillValue(node); 741 assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked()) 742 .isFalse(); 743 } 744 745 /** 746 * Asserts a node exists and has the {@code expected} number of children. 747 */ assertNumberOfChildren(AssistStructure structure, String resourceId, int expected)748 public static void assertNumberOfChildren(AssistStructure structure, String resourceId, 749 int expected) { 750 final ViewNode node = findNodeByResourceId(structure, resourceId); 751 final int actual = node.getChildCount(); 752 if (actual != expected) { 753 dumpStructure("assertNumberOfChildren()", structure); 754 throw new AssertionError("assertNumberOfChildren() for " + resourceId 755 + " failed: expected " + expected + ", got " + actual); 756 } 757 } 758 759 /** 760 * Asserts the number of children in the Assist structure. 761 */ assertNumberOfChildrenWithWindowTitle(AssistStructure structure, int expected, CharSequence windowTitle)762 public static void assertNumberOfChildrenWithWindowTitle(AssistStructure structure, 763 int expected, CharSequence windowTitle) { 764 final int actual = getNumberNodes(structure, windowTitle); 765 if (actual != expected) { 766 dumpStructure("assertNumberOfChildren()", structure); 767 throw new AssertionError("assertNumberOfChildren() for structure failed: expected " 768 + expected + ", got " + actual); 769 } 770 } 771 772 /** 773 * Gets the total number of nodes in an structure. 774 */ getNumberNodes(AssistStructure structure, CharSequence windowTitle)775 public static int getNumberNodes(AssistStructure structure, 776 CharSequence windowTitle) { 777 int count = 0; 778 final int nodes = structure.getWindowNodeCount(); 779 for (int i = 0; i < nodes; i++) { 780 final WindowNode windowNode = structure.getWindowNodeAt(i); 781 if (windowNode.getTitle().equals(windowTitle)) { 782 final ViewNode rootNode = windowNode.getRootViewNode(); 783 count += getNumberNodes(rootNode); 784 } 785 } 786 return count; 787 } 788 789 /** 790 * Gets the activity title. 791 */ getActivityTitle(Instrumentation instrumentation, Activity activity)792 public static CharSequence getActivityTitle(Instrumentation instrumentation, 793 Activity activity) { 794 final StringBuilder titleBuilder = new StringBuilder(); 795 instrumentation.runOnMainSync(() -> titleBuilder.append(activity.getTitle())); 796 return titleBuilder; 797 } 798 799 /** 800 * Gets the total number of nodes in an node, including all descendants and the node itself. 801 */ getNumberNodes(ViewNode node)802 public static int getNumberNodes(ViewNode node) { 803 int count = 1; 804 final int childrenSize = node.getChildCount(); 805 if (childrenSize > 0) { 806 for (int i = 0; i < childrenSize; i++) { 807 count += getNumberNodes(node.getChildAt(i)); 808 } 809 } 810 return count; 811 } 812 813 /** 814 * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given 815 * {@code resourceIds}. 816 */ getAutofillIds(Function<String, ViewNode> nodeResolver, String[] resourceIds)817 public static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver, 818 String[] resourceIds) { 819 if (resourceIds == null) return null; 820 821 final AutofillId[] requiredIds = new AutofillId[resourceIds.length]; 822 for (int i = 0; i < resourceIds.length; i++) { 823 final String resourceId = resourceIds[i]; 824 final ViewNode node = nodeResolver.apply(resourceId); 825 if (node == null) { 826 throw new AssertionError("No node with resourceId " + resourceId); 827 } 828 requiredIds[i] = node.getAutofillId(); 829 830 } 831 return requiredIds; 832 } 833 834 /** 835 * Get an {@link AutofillId} mapped from the {@code structure} node with the given 836 * {@code resourceId}. 837 */ getAutofillId(Function<String, ViewNode> nodeResolver, String resourceId)838 public static AutofillId getAutofillId(Function<String, ViewNode> nodeResolver, 839 String resourceId) { 840 if (resourceId == null) return null; 841 842 final ViewNode node = nodeResolver.apply(resourceId); 843 if (node == null) { 844 throw new AssertionError("No node with resourceId " + resourceId); 845 } 846 return node.getAutofillId(); 847 } 848 849 /** 850 * Prevents the screen to rotate by itself 851 */ disableAutoRotation(UiBot uiBot)852 public static void disableAutoRotation(UiBot uiBot) throws Exception { 853 runShellCommand(ACCELLEROMETER_CHANGE, 0); 854 uiBot.setScreenOrientation(PORTRAIT); 855 } 856 857 /** 858 * Allows the screen to rotate by itself 859 */ allowAutoRotation()860 public static void allowAutoRotation() { 861 runShellCommand(ACCELLEROMETER_CHANGE, 1); 862 } 863 864 /** 865 * Gets the maximum number of partitions per session. 866 */ getMaxPartitions()867 public static int getMaxPartitions() { 868 return Integer.parseInt(runShellCommand("cmd autofill get max_partitions")); 869 } 870 871 /** 872 * Sets the maximum number of partitions per session. 873 */ setMaxPartitions(int value)874 public static void setMaxPartitions(int value) throws Exception { 875 runShellCommand("cmd autofill set max_partitions %d", value); 876 SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_partitions", () -> { 877 return getMaxPartitions() == value ? Boolean.TRUE : null; 878 }); 879 } 880 881 /** 882 * Gets the maximum number of visible datasets. 883 */ getMaxVisibleDatasets()884 public static int getMaxVisibleDatasets() { 885 return Integer.parseInt(runShellCommand("cmd autofill get max_visible_datasets")); 886 } 887 888 /** 889 * Sets the maximum number of visible datasets. 890 */ setMaxVisibleDatasets(int value)891 public static void setMaxVisibleDatasets(int value) throws Exception { 892 runShellCommand("cmd autofill set max_visible_datasets %d", value); 893 SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_visible_datasets", () -> { 894 return getMaxVisibleDatasets() == value ? Boolean.TRUE : null; 895 }); 896 } 897 898 /** 899 * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi. 900 */ isAutofillWindowFullScreen(Context context)901 public static boolean isAutofillWindowFullScreen(Context context) { 902 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); 903 } 904 905 /** 906 * Checks if screen orientation can be changed. 907 */ isRotationSupported(Context context)908 public static boolean isRotationSupported(Context context) { 909 final PackageManager packageManager = context.getPackageManager(); 910 if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 911 Log.v(TAG, "isRotationSupported(): is auto"); 912 return false; 913 } 914 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 915 Log.v(TAG, "isRotationSupported(): has leanback feature"); 916 return false; 917 } 918 if (packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) { 919 Log.v(TAG, "isRotationSupported(): is PC"); 920 return false; 921 } 922 if (!packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE) 923 || !packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)) { 924 Log.v(TAG, "isRotationSupported(): no screen orientation feature"); 925 return false; 926 } 927 return true; 928 } 929 getBoolean(Context context, String id)930 private static boolean getBoolean(Context context, String id) { 931 final Resources resources = context.getResources(); 932 final int booleanId = resources.getIdentifier(id, "bool", "android"); 933 return resources.getBoolean(booleanId); 934 } 935 936 /** 937 * Uses Shell command to get the Autofill logging level. 938 */ getLoggingLevel()939 public static String getLoggingLevel() { 940 return runShellCommand("cmd autofill get log_level"); 941 } 942 943 /** 944 * Uses Shell command to set the Autofill logging level. 945 */ setLoggingLevel(String level)946 public static void setLoggingLevel(String level) { 947 runShellCommand("cmd autofill set log_level %s", level); 948 } 949 950 /** 951 * Uses Settings to enable the given autofill service for the default user, and checks the 952 * value was properly check, throwing an exception if it was not. 953 */ enableAutofillService(@onNull Context context, @NonNull String serviceName)954 public static void enableAutofillService(@NonNull Context context, 955 @NonNull String serviceName) { 956 if (isAutofillServiceEnabled(serviceName)) return; 957 958 // Sets the setting synchronously. Note that the config itself is sets synchronously but 959 // launch of the service is asynchronous after the config is updated. 960 SettingsUtils.syncSet(context, AUTOFILL_SERVICE, serviceName); 961 962 // Waits until the service is actually enabled. 963 try { 964 Timeouts.CONNECTION_TIMEOUT.run("Enabling Autofill service", () -> { 965 return isAutofillServiceEnabled(serviceName) ? serviceName : null; 966 }); 967 } catch (Exception e) { 968 throw new AssertionError("Enabling Autofill service failed."); 969 } 970 } 971 972 /** 973 * Uses Settings to disable the given autofill service for the default user, and waits until 974 * the setting is deleted. 975 */ disableAutofillService(@onNull Context context)976 public static void disableAutofillService(@NonNull Context context) { 977 final String currentService = SettingsUtils.get(AUTOFILL_SERVICE); 978 if (currentService == null) { 979 Log.v(TAG, "disableAutofillService(): already disabled"); 980 return; 981 } 982 Log.v(TAG, "Disabling " + currentService); 983 SettingsUtils.syncDelete(context, AUTOFILL_SERVICE); 984 } 985 986 /** 987 * Checks whether the given service is set as the autofill service for the default user. 988 */ isAutofillServiceEnabled(@onNull String serviceName)989 public static boolean isAutofillServiceEnabled(@NonNull String serviceName) { 990 final String actualName = getAutofillServiceName(); 991 return serviceName.equals(actualName); 992 } 993 994 /** 995 * Gets then name of the autofill service for the default user. 996 */ getAutofillServiceName()997 public static String getAutofillServiceName() { 998 return SettingsUtils.get(AUTOFILL_SERVICE); 999 } 1000 1001 /** 1002 * Asserts whether the given service is enabled as the autofill service for the default user. 1003 */ assertAutofillServiceStatus(@onNull String serviceName, boolean enabled)1004 public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) { 1005 final String actual = SettingsUtils.get(AUTOFILL_SERVICE); 1006 final String expected = enabled ? serviceName : null; 1007 assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE) 1008 .that(actual).isEqualTo(expected); 1009 } 1010 1011 /** 1012 * Enables / disables the default augmented autofill service. 1013 */ setDefaultAugmentedAutofillServiceEnabled(boolean enabled)1014 public static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) { 1015 Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled); 1016 runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s", 1017 Boolean.toString(enabled)); 1018 } 1019 1020 /** 1021 * Gets the instrumentation context. 1022 */ getContext()1023 public static Context getContext() { 1024 return InstrumentationRegistry.getInstrumentation().getContext(); 1025 } 1026 1027 /** 1028 * Asserts the node has an {@code HTMLInfo} property, with the given tag. 1029 */ assertHasHtmlTag(ViewNode node, String expectedTag)1030 public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) { 1031 final HtmlInfo info = node.getHtmlInfo(); 1032 assertWithMessage("node doesn't have htmlInfo").that(info).isNotNull(); 1033 assertWithMessage("wrong tag").that(info.getTag()).isEqualTo(expectedTag); 1034 return info; 1035 } 1036 1037 /** 1038 * Gets the value of an {@code HTMLInfo} attribute. 1039 */ 1040 @Nullable getAttributeValue(HtmlInfo info, String attribute)1041 public static String getAttributeValue(HtmlInfo info, String attribute) { 1042 for (Pair<String, String> pair : info.getAttributes()) { 1043 if (pair.first.equals(attribute)) { 1044 return pair.second; 1045 } 1046 } 1047 return null; 1048 } 1049 1050 /** 1051 * Asserts a {@code HTMLInfo} has an attribute with a given value. 1052 */ assertHasAttribute(HtmlInfo info, String attribute, String expectedValue)1053 public static void assertHasAttribute(HtmlInfo info, String attribute, String expectedValue) { 1054 final String actualValue = getAttributeValue(info, attribute); 1055 assertWithMessage("Attribute %s not found", attribute).that(actualValue).isNotNull(); 1056 assertWithMessage("Wrong value for Attribute %s", attribute) 1057 .that(actualValue).isEqualTo(expectedValue); 1058 } 1059 1060 /** 1061 * Finds a {@link WebView} node given its expected form name. 1062 */ findWebViewNodeByFormName(AssistStructure structure, String formName)1063 public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) { 1064 return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER); 1065 } 1066 assertClientState(Object container, Bundle clientState, String key, String value)1067 private static void assertClientState(Object container, Bundle clientState, 1068 String key, String value) { 1069 assertWithMessage("'%s' should have client state", container) 1070 .that(clientState).isNotNull(); 1071 assertWithMessage("Wrong number of client state extras on '%s'", container) 1072 .that(clientState.keySet().size()).isEqualTo(1); 1073 assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container) 1074 .that(clientState.getString(key)).isEqualTo(value); 1075 } 1076 1077 /** 1078 * Asserts the content of a {@link FillEventHistory#getClientState()}. 1079 * 1080 * @param history event to be asserted 1081 * @param key the only key expected in the client state bundle 1082 * @param value the only value expected in the client state bundle 1083 */ 1084 @SuppressWarnings("javadoc") assertDeprecatedClientState(@onNull FillEventHistory history, @NonNull String key, @NonNull String value)1085 public static void assertDeprecatedClientState(@NonNull FillEventHistory history, 1086 @NonNull String key, @NonNull String value) { 1087 assertThat(history).isNotNull(); 1088 @SuppressWarnings("deprecation") 1089 final Bundle clientState = history.getClientState(); 1090 assertClientState(history, clientState, key, value); 1091 } 1092 1093 /** 1094 * Asserts the {@link FillEventHistory#getClientState()} is not set. 1095 * 1096 * @param history event to be asserted 1097 */ 1098 @SuppressWarnings("javadoc") assertNoDeprecatedClientState(@onNull FillEventHistory history)1099 public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) { 1100 assertThat(history).isNotNull(); 1101 @SuppressWarnings("deprecation") 1102 final Bundle clientState = history.getClientState(); 1103 assertWithMessage("History '%s' should not have client state", history) 1104 .that(clientState).isNull(); 1105 } 1106 1107 /** 1108 * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}. 1109 * 1110 * @param event event to be asserted 1111 * @param eventType expected type 1112 * @param datasetId dataset set id expected in the event 1113 * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't 1114 * have client state) 1115 * @param value the only value expected in the client state bundle (or {@code null} if it 1116 * shouldn't have client state) 1117 * @param fieldClassificationResults expected results when asserting field classification 1118 */ assertFillEvent(@onNull FillEventHistory.Event event, int eventType, @Nullable String datasetId, @Nullable String key, @Nullable String value, @Nullable FieldClassificationResult[] fieldClassificationResults)1119 private static void assertFillEvent(@NonNull FillEventHistory.Event event, 1120 int eventType, @Nullable String datasetId, 1121 @Nullable String key, @Nullable String value, 1122 @Nullable FieldClassificationResult[] fieldClassificationResults) { 1123 assertThat(event).isNotNull(); 1124 assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType); 1125 if (datasetId == null) { 1126 assertWithMessage("Event %s should not have dataset id", event) 1127 .that(event.getDatasetId()).isNull(); 1128 } else { 1129 assertWithMessage("Wrong dataset id for %s", event) 1130 .that(event.getDatasetId()).isEqualTo(datasetId); 1131 } 1132 final Bundle clientState = event.getClientState(); 1133 if (key == null) { 1134 assertWithMessage("Event '%s' should not have client state", event) 1135 .that(clientState).isNull(); 1136 } else { 1137 assertClientState(event, clientState, key, value); 1138 } 1139 assertWithMessage("Event '%s' should not have selected datasets", event) 1140 .that(event.getSelectedDatasetIds()).isEmpty(); 1141 assertWithMessage("Event '%s' should not have ignored datasets", event) 1142 .that(event.getIgnoredDatasetIds()).isEmpty(); 1143 assertWithMessage("Event '%s' should not have changed fields", event) 1144 .that(event.getChangedFields()).isEmpty(); 1145 assertWithMessage("Event '%s' should not have manually-entered fields", event) 1146 .that(event.getManuallyEnteredField()).isEmpty(); 1147 final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification(); 1148 if (fieldClassificationResults == null) { 1149 assertThat(detectedFields).isEmpty(); 1150 } else { 1151 assertThat(detectedFields).hasSize(fieldClassificationResults.length); 1152 int i = 0; 1153 for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) { 1154 assertMatches(i, entry, fieldClassificationResults[i]); 1155 i++; 1156 } 1157 } 1158 } 1159 assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult, FieldClassificationResult expectedResult)1160 private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult, 1161 FieldClassificationResult expectedResult) { 1162 assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey()) 1163 .isEqualTo(expectedResult.id); 1164 final List<Match> matches = actualResult.getValue().getMatches(); 1165 assertWithMessage("Wrong number of matches: " + matches).that(matches.size()) 1166 .isEqualTo(expectedResult.categoryIds.length); 1167 for (int j = 0; j < matches.size(); j++) { 1168 final Match match = matches.get(j); 1169 assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match) 1170 .that(match.getCategoryId()).isEqualTo(expectedResult.categoryIds[j]); 1171 assertWithMessage("Wrong score at (%s, %s): %s", i, j, match) 1172 .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]); 1173 } 1174 } 1175 1176 /** 1177 * Asserts the content of a 1178 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event. 1179 * 1180 * @param event event to be asserted 1181 * @param datasetId dataset set id expected in the event 1182 */ assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId)1183 public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event, 1184 @Nullable String datasetId) { 1185 assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null); 1186 } 1187 1188 /** 1189 * Asserts the content of a 1190 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event. 1191 * 1192 * @param event event to be asserted 1193 * @param datasetId dataset set id expected in the event 1194 * @param key the only key expected in the client state bundle 1195 * @param value the only value expected in the client state bundle 1196 */ assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId, @Nullable String key, @Nullable String value)1197 public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event, 1198 @Nullable String datasetId, @Nullable String key, @Nullable String value) { 1199 assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null); 1200 } 1201 1202 /** 1203 * Asserts the content of a 1204 * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event. 1205 * 1206 * @param event event to be asserted 1207 * @param datasetId dataset set id expected in the event 1208 * @param key the only key expected in the client state bundle 1209 * @param value the only value expected in the client state bundle 1210 */ assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1211 public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event, 1212 @Nullable String datasetId, @NonNull String key, @NonNull String value) { 1213 assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null); 1214 } 1215 1216 /** 1217 * Asserts the content of a 1218 * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event. 1219 * 1220 * @param event event to be asserted 1221 * @param datasetId dataset set id expected in the event 1222 */ assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @Nullable String datasetId)1223 public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event, 1224 @Nullable String datasetId) { 1225 assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null); 1226 } 1227 1228 /** 1229 * Asserts the content of a 1230 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event. 1231 * 1232 * @param event event to be asserted 1233 * @param key the only key expected in the client state bundle 1234 * @param value the only value expected in the client state bundle 1235 */ assertFillEventForDatasetShown(@onNull FillEventHistory.Event event, @NonNull String key, @NonNull String value)1236 public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event, 1237 @NonNull String key, @NonNull String value) { 1238 assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null); 1239 } 1240 1241 /** 1242 * Asserts the content of a 1243 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event. 1244 * 1245 * @param event event to be asserted 1246 */ assertFillEventForDatasetShown(@onNull FillEventHistory.Event event)1247 public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event) { 1248 assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null); 1249 } 1250 1251 /** 1252 * Asserts the content of a 1253 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED} 1254 * event. 1255 * 1256 * @param event event to be asserted 1257 * @param datasetId dataset set id expected in the event 1258 * @param key the only key expected in the client state bundle 1259 * @param value the only value expected in the client state bundle 1260 */ assertFillEventForDatasetAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1261 public static void assertFillEventForDatasetAuthenticationSelected( 1262 @NonNull FillEventHistory.Event event, 1263 @Nullable String datasetId, @NonNull String key, @NonNull String value) { 1264 assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null); 1265 } 1266 1267 /** 1268 * Asserts the content of a 1269 * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event. 1270 * 1271 * @param event event to be asserted 1272 * @param datasetId dataset set id expected in the event 1273 * @param key the only key expected in the client state bundle 1274 * @param value the only value expected in the client state bundle 1275 */ assertFillEventForAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1276 public static void assertFillEventForAuthenticationSelected( 1277 @NonNull FillEventHistory.Event event, 1278 @Nullable String datasetId, @NonNull String key, @NonNull String value) { 1279 assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null); 1280 } 1281 assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull AutofillId fieldId, @NonNull String categoryId, float score)1282 public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event, 1283 @NonNull AutofillId fieldId, @NonNull String categoryId, float score) { 1284 assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, 1285 new FieldClassificationResult[] { 1286 new FieldClassificationResult(fieldId, categoryId, score) 1287 }); 1288 } 1289 assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull FieldClassificationResult[] results)1290 public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event, 1291 @NonNull FieldClassificationResult[] results) { 1292 assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results); 1293 } 1294 assertFillEventForContextCommitted(@onNull FillEventHistory.Event event)1295 public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) { 1296 assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null); 1297 } 1298 1299 @NonNull getActivityName(List<FillContext> contexts)1300 public static String getActivityName(List<FillContext> contexts) { 1301 if (contexts == null) return "N/A (null contexts)"; 1302 1303 if (contexts.isEmpty()) return "N/A (empty contexts)"; 1304 1305 final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure(); 1306 if (structure == null) return "N/A (no AssistStructure)"; 1307 1308 final ComponentName componentName = structure.getActivityComponent(); 1309 if (componentName == null) return "N/A (no component name)"; 1310 1311 return componentName.flattenToShortString(); 1312 } 1313 assertFloat(float actualValue, float expectedValue)1314 public static void assertFloat(float actualValue, float expectedValue) { 1315 assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue); 1316 } 1317 assertHasFlags(int actualFlags, int expectedFlags)1318 public static void assertHasFlags(int actualFlags, int expectedFlags) { 1319 assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags) 1320 .that(actualFlags & expectedFlags).isEqualTo(expectedFlags); 1321 } 1322 callbackEventAsString(int event)1323 public static String callbackEventAsString(int event) { 1324 switch (event) { 1325 case AutofillCallback.EVENT_INPUT_HIDDEN: 1326 return "HIDDEN"; 1327 case AutofillCallback.EVENT_INPUT_SHOWN: 1328 return "SHOWN"; 1329 case AutofillCallback.EVENT_INPUT_UNAVAILABLE: 1330 return "UNAVAILABLE"; 1331 default: 1332 return "UNKNOWN:" + event; 1333 } 1334 } 1335 importantForAutofillAsString(int mode)1336 public static String importantForAutofillAsString(int mode) { 1337 switch (mode) { 1338 case View.IMPORTANT_FOR_AUTOFILL_AUTO: 1339 return "IMPORTANT_FOR_AUTOFILL_AUTO"; 1340 case View.IMPORTANT_FOR_AUTOFILL_YES: 1341 return "IMPORTANT_FOR_AUTOFILL_YES"; 1342 case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS: 1343 return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS"; 1344 case View.IMPORTANT_FOR_AUTOFILL_NO: 1345 return "IMPORTANT_FOR_AUTOFILL_NO"; 1346 case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS: 1347 return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS"; 1348 default: 1349 return "UNKNOWN:" + mode; 1350 } 1351 } 1352 hasHint(@ullable String[] hints, @Nullable Object expectedHint)1353 public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) { 1354 if (hints == null || expectedHint == null) return false; 1355 for (String actualHint : hints) { 1356 if (expectedHint.equals(actualHint)) return true; 1357 } 1358 return false; 1359 } 1360 newClientState(String key, String value)1361 public static Bundle newClientState(String key, String value) { 1362 final Bundle clientState = new Bundle(); 1363 clientState.putString(key, value); 1364 return clientState; 1365 } 1366 assertAuthenticationClientState(String where, Bundle data, String expectedKey, String expectedValue)1367 public static void assertAuthenticationClientState(String where, Bundle data, 1368 String expectedKey, String expectedValue) { 1369 assertWithMessage("no client state on %s", where).that(data).isNotNull(); 1370 final String extraValue = data.getString(expectedKey); 1371 assertWithMessage("invalid value for %s on %s", expectedKey, where) 1372 .that(extraValue).isEqualTo(expectedValue); 1373 } 1374 1375 /** 1376 * Asserts that 2 bitmaps have are the same. If they aren't throws an exception and dump them 1377 * locally so their can be visually inspected. 1378 * 1379 * @param filename base name of the files generated in case of error 1380 * @param bitmap1 first bitmap to be compared 1381 * @param bitmap2 second bitmap to be compared 1382 */ 1383 // TODO: move to common code assertBitmapsAreSame(@onNull String filename, @Nullable Bitmap bitmap1, @Nullable Bitmap bitmap2)1384 public static void assertBitmapsAreSame(@NonNull String filename, @Nullable Bitmap bitmap1, 1385 @Nullable Bitmap bitmap2) throws IOException { 1386 assertWithMessage("1st bitmap is null").that(bitmap1).isNotNull(); 1387 assertWithMessage("2nd bitmap is null").that(bitmap2).isNotNull(); 1388 final boolean same = bitmap1.sameAs(bitmap2); 1389 if (same) { 1390 Log.v(TAG, "bitmap comparison passed for " + filename); 1391 return; 1392 } 1393 1394 final File dir = getLocalDirectory(); 1395 if (dir == null) { 1396 throw new AssertionError("bitmap comparison failed for " + filename 1397 + ", and bitmaps could not be dumped on " + dir); 1398 } 1399 final File dump1 = dumpBitmap(bitmap1, dir, filename + "-1.png"); 1400 final File dump2 = dumpBitmap(bitmap2, dir, filename + "-2.png"); 1401 throw new AssertionError( 1402 "bitmap comparison failed; check contents of " + dump1 + " and " + dump2); 1403 } 1404 1405 @Nullable getLocalDirectory()1406 private static File getLocalDirectory() { 1407 final File dir = new File(LOCAL_DIRECTORY); 1408 dir.mkdirs(); 1409 if (!dir.exists()) { 1410 Log.e(TAG, "Could not create directory " + dir); 1411 return null; 1412 } 1413 return dir; 1414 } 1415 1416 @Nullable createFile(@onNull File dir, @NonNull String filename)1417 private static File createFile(@NonNull File dir, @NonNull String filename) throws IOException { 1418 final File file = new File(dir, filename); 1419 if (file.exists()) { 1420 Log.v(TAG, "Deleting file " + file); 1421 file.delete(); 1422 } 1423 if (!file.createNewFile()) { 1424 Log.e(TAG, "Could not create file " + file); 1425 return null; 1426 } 1427 return file; 1428 } 1429 1430 @Nullable dumpBitmap(@onNull Bitmap bitmap, @NonNull File dir, @NonNull String filename)1431 private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir, 1432 @NonNull String filename) throws IOException { 1433 final File file = createFile(dir, filename); 1434 if (file != null) { 1435 dumpBitmap(bitmap, file); 1436 1437 } 1438 return file; 1439 } 1440 1441 @Nullable dumpBitmap(@onNull Bitmap bitmap, @NonNull File file)1442 public static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File file) { 1443 Log.i(TAG, "Dumping bitmap at " + file); 1444 BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName()); 1445 return file; 1446 } 1447 1448 /** 1449 * Creates a file in the device, using the name of the current test as a prefix. 1450 */ 1451 @Nullable createTestFile(@onNull String name)1452 public static File createTestFile(@NonNull String name) throws IOException { 1453 final File dir = getLocalDirectory(); 1454 if (dir == null) return null; 1455 1456 final String prefix = TestNameUtils.getCurrentTestName().replaceAll("\\.|\\(|\\/", "_") 1457 .replaceAll("\\)", ""); 1458 final String filename = prefix + "-" + name; 1459 1460 return createFile(dir, filename); 1461 } 1462 1463 /** 1464 * Offers an object to a queue or times out. 1465 * 1466 * @return {@code true} if the offer was accepted, {$code false} if it timed out or was 1467 * interrupted. 1468 */ offer(BlockingQueue<T> queue, T obj, long timeoutMs)1469 public static <T> boolean offer(BlockingQueue<T> queue, T obj, long timeoutMs) { 1470 boolean offered = false; 1471 try { 1472 offered = queue.offer(obj, timeoutMs, TimeUnit.MILLISECONDS); 1473 } catch (InterruptedException e) { 1474 Log.w(TAG, "interrupted offering", e); 1475 Thread.currentThread().interrupt(); 1476 } 1477 if (!offered) { 1478 Log.e(TAG, "could not offer " + obj + " in " + timeoutMs + "ms"); 1479 } 1480 return offered; 1481 } 1482 1483 /** 1484 * Calls this method to assert given {@code string} is equal to {@link #LARGE_STRING}, as 1485 * comparing its value using standard assertions might ANR. 1486 */ assertEqualsToLargeString(@onNull String string)1487 public static void assertEqualsToLargeString(@NonNull String string) { 1488 assertThat(string).isNotNull(); 1489 assertThat(string).hasLength(LARGE_STRING_SIZE); 1490 assertThat(string.charAt(0)).isEqualTo(LARGE_STRING_CHAR); 1491 assertThat(string.charAt(LARGE_STRING_SIZE - 1)).isEqualTo(LARGE_STRING_CHAR); 1492 } 1493 1494 /** 1495 * Asserts that autofill is enabled in the context, retrying if necessariy. 1496 */ assertAutofillEnabled(@onNull Context context, boolean expected)1497 public static void assertAutofillEnabled(@NonNull Context context, boolean expected) 1498 throws Exception { 1499 assertAutofillEnabled(context.getSystemService(AutofillManager.class), expected); 1500 } 1501 1502 /** 1503 * Asserts that autofill is enabled in the manager, retrying if necessariy. 1504 */ assertAutofillEnabled(@onNull AutofillManager afm, boolean expected)1505 public static void assertAutofillEnabled(@NonNull AutofillManager afm, boolean expected) 1506 throws Exception { 1507 Timeouts.IDLE_UNBIND_TIMEOUT.run("assertEnabled(" + expected + ")", () -> { 1508 final boolean actual = afm.isEnabled(); 1509 Log.v(TAG, "assertEnabled(): expected=" + expected + ", actual=" + actual); 1510 return actual == expected ? "not_used" : null; 1511 }); 1512 } 1513 1514 /** 1515 * Asserts these autofill ids are the same, except for the session. 1516 */ assertEqualsIgnoreSession(@onNull AutofillId id1, @NonNull AutofillId id2)1517 public static void assertEqualsIgnoreSession(@NonNull AutofillId id1, @NonNull AutofillId id2) { 1518 assertWithMessage("id1 is null").that(id1).isNotNull(); 1519 assertWithMessage("id2 is null").that(id2).isNotNull(); 1520 assertWithMessage("%s is not equal to %s", id1, id2).that(id1.equalsIgnoreSession(id2)) 1521 .isTrue(); 1522 } 1523 1524 /** 1525 * Asserts {@link View#isAutofilled()} state of the given view, waiting if necessarity to avoid 1526 * race conditions. 1527 */ assertViewAutofillState(@onNull View view, boolean expected)1528 public static void assertViewAutofillState(@NonNull View view, boolean expected) 1529 throws Exception { 1530 Timeouts.FILL_TIMEOUT.run("assertViewAutofillState(" + view + ", " + expected + ")", 1531 () -> { 1532 final boolean actual = view.isAutofilled(); 1533 Log.v(TAG, "assertViewAutofillState(): expected=" + expected + ", actual=" 1534 + actual); 1535 return actual == expected ? "not_used" : null; 1536 }); 1537 } 1538 1539 /** 1540 * Allows the test to draw overlaid windows. 1541 * 1542 * <p>Should call {@link #disallowOverlays()} afterwards. 1543 */ allowOverlays()1544 public static void allowOverlays() { 1545 ShellUtils.setOverlayPermissions(MY_PACKAGE, true); 1546 } 1547 1548 /** 1549 * Disallow the test to draw overlaid windows. 1550 * 1551 * <p>Should call {@link #disallowOverlays()} afterwards. 1552 */ disallowOverlays()1553 public static void disallowOverlays() { 1554 ShellUtils.setOverlayPermissions(MY_PACKAGE, false); 1555 } 1556 createPresentation(String message)1557 public static RemoteViews createPresentation(String message) { 1558 final RemoteViews presentation = new RemoteViews(getContext() 1559 .getPackageName(), R.layout.list_item); 1560 presentation.setTextViewText(R.id.text1, message); 1561 return presentation; 1562 } 1563 createInlinePresentation(String message)1564 public static InlinePresentation createInlinePresentation(String message) { 1565 final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 1566 PendingIntent.FLAG_IMMUTABLE); 1567 return createInlinePresentation(message, dummyIntent, false); 1568 } 1569 createInlinePresentation(String message, PendingIntent attribution)1570 public static InlinePresentation createInlinePresentation(String message, 1571 PendingIntent attribution) { 1572 return createInlinePresentation(message, attribution, false); 1573 } 1574 createPinnedInlinePresentation(String message)1575 public static InlinePresentation createPinnedInlinePresentation(String message) { 1576 final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 1577 PendingIntent.FLAG_IMMUTABLE); 1578 return createInlinePresentation(message, dummyIntent, true); 1579 } 1580 createInlinePresentation(@onNull String message, @NonNull PendingIntent attribution, boolean pinned)1581 private static InlinePresentation createInlinePresentation(@NonNull String message, 1582 @NonNull PendingIntent attribution, boolean pinned) { 1583 return new InlinePresentation( 1584 InlineSuggestionUi.newContentBuilder(attribution) 1585 .setTitle(message).build().getSlice(), 1586 new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100)) 1587 .build(), /* pinned= */ pinned); 1588 } 1589 createInlineTooltipPresentation( @onNull String message)1590 public static InlinePresentation createInlineTooltipPresentation( 1591 @NonNull String message) { 1592 final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 1593 PendingIntent.FLAG_IMMUTABLE); 1594 return createInlineTooltipPresentation(message, dummyIntent); 1595 } 1596 createInlineTooltipPresentation( @onNull String message, @NonNull PendingIntent attribution)1597 private static InlinePresentation createInlineTooltipPresentation( 1598 @NonNull String message, @NonNull PendingIntent attribution) { 1599 return InlinePresentation.createTooltipPresentation( 1600 InlineSuggestionUi.newContentBuilder(attribution) 1601 .setTitle(message).build().getSlice(), 1602 new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100)) 1603 .build()); 1604 } 1605 mockSwitchInputMethod(@onNull Context context)1606 public static void mockSwitchInputMethod(@NonNull Context context) throws Exception { 1607 final ContentResolver cr = context.getContentResolver(); 1608 final int subtype = Settings.Secure.getInt(cr, SELECTED_INPUT_METHOD_SUBTYPE); 1609 Settings.Secure.putInt(cr, SELECTED_INPUT_METHOD_SUBTYPE, subtype); 1610 } 1611 1612 /** 1613 * Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist. 1614 */ resetApplicationAutofillOptions(@onNull Context context)1615 public static void resetApplicationAutofillOptions(@NonNull Context context) { 1616 AutofillOptions options = AutofillOptions.forWhitelistingItself(); 1617 options.augmentedAutofillEnabled = false; 1618 context.getApplicationContext().setAutofillOptions(options); 1619 } 1620 1621 /** 1622 * Clear AutofillOptions. 1623 */ clearApplicationAutofillOptions(@onNull Context context)1624 public static void clearApplicationAutofillOptions(@NonNull Context context) { 1625 context.getApplicationContext().setAutofillOptions(null); 1626 } 1627 Helper()1628 private Helper() { 1629 throw new UnsupportedOperationException("contain static methods only"); 1630 } 1631 1632 public static class FieldClassificationResult { 1633 public final AutofillId id; 1634 public final String[] categoryIds; 1635 public final float[] scores; 1636 FieldClassificationResult(@onNull AutofillId id, @NonNull String categoryId, float score)1637 public FieldClassificationResult(@NonNull AutofillId id, @NonNull String categoryId, 1638 float score) { 1639 this(id, new String[]{categoryId}, new float[]{score}); 1640 } 1641 FieldClassificationResult(@onNull AutofillId id, @NonNull String[] categoryIds, float[] scores)1642 public FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] categoryIds, 1643 float[] scores) { 1644 this.id = id; 1645 this.categoryIds = categoryIds; 1646 this.scores = scores; 1647 } 1648 } 1649 } 1650