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; 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_DATASETS_SHOWN; 27 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED; 28 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED; 29 import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN; 30 import static android.service.autofill.FillEventHistory.Event.TYPE_VIEW_REQUESTED_AUTOFILL; 31 32 import static com.android.compatibility.common.util.ShellUtils.runShellCommand; 33 34 import static com.google.common.truth.Truth.assertThat; 35 import static com.google.common.truth.Truth.assertWithMessage; 36 37 import android.app.Activity; 38 import android.app.ActivityOptions; 39 import android.app.Instrumentation; 40 import android.app.PendingIntent; 41 import android.app.assist.AssistStructure; 42 import android.app.assist.AssistStructure.ViewNode; 43 import android.app.assist.AssistStructure.WindowNode; 44 import android.autofillservice.cts.R; 45 import android.autofillservice.cts.activities.AbstractAutoFillActivity; 46 import android.content.AutofillOptions; 47 import android.content.ComponentName; 48 import android.content.ContentResolver; 49 import android.content.Context; 50 import android.content.Intent; 51 import android.content.pm.PackageManager; 52 import android.content.res.Resources; 53 import android.graphics.Bitmap; 54 import android.hardware.devicestate.DeviceState; 55 import android.hardware.devicestate.DeviceStateManager; 56 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; 57 import android.icu.util.Calendar; 58 import android.os.Bundle; 59 import android.os.Environment; 60 import android.os.UserManager; 61 import android.provider.DeviceConfig; 62 import android.provider.Settings; 63 import android.service.autofill.CustomDescription; 64 import android.service.autofill.FieldClassification; 65 import android.service.autofill.FieldClassification.Match; 66 import android.service.autofill.FillContext; 67 import android.service.autofill.FillEventHistory; 68 import android.service.autofill.InlinePresentation; 69 import android.text.TextUtils; 70 import android.util.Log; 71 import android.util.Pair; 72 import android.util.Size; 73 import android.view.View; 74 import android.view.ViewGroup; 75 import android.view.ViewStructure.HtmlInfo; 76 import android.view.WindowInsets; 77 import android.view.autofill.AutofillFeatureFlags; 78 import android.view.autofill.AutofillId; 79 import android.view.autofill.AutofillManager; 80 import android.view.autofill.AutofillManager.AutofillCallback; 81 import android.view.autofill.AutofillValue; 82 import android.webkit.WebView; 83 import android.widget.RemoteViews; 84 import android.widget.inline.InlinePresentationSpec; 85 86 import androidx.annotation.NonNull; 87 import androidx.annotation.Nullable; 88 import androidx.autofill.inline.v1.InlineSuggestionUi; 89 import androidx.test.platform.app.InstrumentationRegistry; 90 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; 91 import androidx.test.runner.lifecycle.Stage; 92 93 import com.android.compatibility.common.util.BitmapUtils; 94 import com.android.compatibility.common.util.DeviceConfigStateManager; 95 import com.android.compatibility.common.util.OneTimeSettingsListener; 96 import com.android.compatibility.common.util.ShellUtils; 97 import com.android.compatibility.common.util.TestNameUtils; 98 import com.android.compatibility.common.util.Timeout; 99 import com.android.compatibility.common.util.UserSettings; 100 101 import java.io.File; 102 import java.io.IOException; 103 import java.util.ArrayList; 104 import java.util.Arrays; 105 import java.util.Collection; 106 import java.util.List; 107 import java.util.Map; 108 import java.util.Map.Entry; 109 import java.util.concurrent.BlockingQueue; 110 import java.util.concurrent.TimeUnit; 111 import java.util.function.Consumer; 112 import java.util.function.Function; 113 import java.util.regex.Pattern; 114 115 /** 116 * Helper for common funcionalities. 117 */ 118 public final class Helper { 119 120 public static final String TAG = "AutoFillCtsHelper"; 121 122 public static final boolean VERBOSE = false; 123 124 public static final String MY_PACKAGE = "android.autofillservice.cts"; 125 126 public static final String ID_USERNAME_LABEL = "username_label"; 127 public static final String ID_USERNAME = "username"; 128 public static final String ID_PASSWORD_LABEL = "password_label"; 129 public static final String ID_PASSWORD = "password"; 130 public static final String ID_CARD_NUMBER = "card_number"; 131 public static final String ID_LOGIN = "login"; 132 public static final String ID_OUTPUT = "output"; 133 public static final String ID_STATIC_TEXT = "static_text"; 134 public static final String ID_EMPTY = "empty"; 135 public static final String ID_CANCEL_FILL = "cancel_fill"; 136 public static final String ID_IMEACTION_TEXT = "ime_option_text"; 137 public static final String ID_IMEACTION_TEXT_IMPORTANT_FOR_AUTOFILL = 138 "ime_option_text_important_for_autofill"; 139 public static final String ID_IMEACTION_LABEL = "ime_option_text_label"; 140 141 public static final String NULL_DATASET_ID = null; 142 143 public static final char LARGE_STRING_CHAR = '6'; 144 // NOTE: cannot be much large as it could ANR and fail the test. 145 public static final int LARGE_STRING_SIZE = 100_000; 146 public static final String LARGE_STRING = com.android.compatibility.common.util.TextUtils 147 .repeat(LARGE_STRING_CHAR, LARGE_STRING_SIZE); 148 149 /** 150 * Can be used in cases where the autofill values is required by irrelevant (like adding a 151 * value to an authenticated dataset). 152 */ 153 public static final String UNUSED_AUTOFILL_VALUE = null; 154 155 private static final String ACCELLEROMETER_CHANGE = 156 "content insert --uri content://settings/system --bind name:s:accelerometer_rotation " 157 + "--bind value:i:%d"; 158 159 private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory() 160 + "/CtsAutoFillServiceTestCases"; 161 162 private static final Timeout SETTINGS_BASED_SHELL_CMD_TIMEOUT = new Timeout( 163 "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2, 164 OneTimeSettingsListener.DEFAULT_TIMEOUT_MS); 165 166 public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS = "autofill_dialog_hints"; 167 168 private static final UserSettings sUserSettings = new UserSettings(); 169 170 /** 171 * Helper interface used to filter nodes. 172 * 173 * @param <T> node type 174 */ 175 interface NodeFilter<T> { 176 /** 177 * Returns whether the node passes the filter for such given id. 178 */ matches(T node, Object id)179 boolean matches(T node, Object id); 180 } 181 182 private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> { 183 return id.equals(node.getIdEntry()); 184 }; 185 186 private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> { 187 return id.equals(getHtmlName(node)); 188 }; 189 190 private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> { 191 return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry()); 192 }; 193 194 private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> { 195 return id.equals(node.getText()); 196 }; 197 198 private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> { 199 return hasHint(node.getAutofillHints(), id); 200 }; 201 202 private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> { 203 final String className = node.getClassName(); 204 if (!className.equals("android.webkit.WebView")) return false; 205 206 final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form"); 207 final String formName = getAttributeValue(htmlInfo, "name"); 208 return id.equals(formName); 209 }; 210 211 private static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> { 212 return hasHint(view.getAutofillHints(), id); 213 }; 214 toString(AssistStructure structure, StringBuilder builder)215 private static String toString(AssistStructure structure, StringBuilder builder) { 216 builder.append("[component=").append(structure.getActivityComponent()); 217 final int nodes = structure.getWindowNodeCount(); 218 for (int i = 0; i < nodes; i++) { 219 final WindowNode windowNode = structure.getWindowNodeAt(i); 220 dump(builder, windowNode.getRootViewNode(), " ", 0); 221 } 222 return builder.append(']').toString(); 223 } 224 225 @NonNull toString(@onNull AssistStructure structure)226 public static String toString(@NonNull AssistStructure structure) { 227 return toString(structure, new StringBuilder()); 228 } 229 230 @Nullable toString(@ullable AutofillValue value)231 public static String toString(@Nullable AutofillValue value) { 232 if (value == null) return null; 233 if (value.isText()) { 234 // We don't care about PII... 235 final CharSequence text = value.getTextValue(); 236 return text == null ? null : text.toString(); 237 } 238 return value.toString(); 239 } 240 241 /** 242 * Dump the assist structure on logcat. 243 */ dumpStructure(String message, AssistStructure structure)244 public static void dumpStructure(String message, AssistStructure structure) { 245 Log.i(TAG, toString(structure, new StringBuilder(message))); 246 } 247 248 /** 249 * Dump the contexts on logcat. 250 */ dumpStructure(String message, List<FillContext> contexts)251 public static void dumpStructure(String message, List<FillContext> contexts) { 252 for (FillContext context : contexts) { 253 dumpStructure(message, context.getStructure()); 254 } 255 } 256 257 /** 258 * Dumps the state of the autofill service on logcat. 259 */ dumpAutofillService(@onNull String tag)260 public static void dumpAutofillService(@NonNull String tag) { 261 final String autofillDump = runShellCommand("dumpsys autofill"); 262 Log.i(tag, "dumpsys autofill\n\n" + autofillDump); 263 final String myServiceDump = runShellCommand("dumpsys activity service %s", 264 InstrumentedAutoFillService.SERVICE_NAME); 265 Log.i(tag, "my service dump: \n" + myServiceDump); 266 } 267 268 /** 269 * Dumps the state of {@link android.service.autofill.InlineSuggestionRenderService}, and assert 270 * that it says the number of active inline suggestion views is the given number. 271 * 272 * <p>Note that ideally we should have a test api to fetch the number and verify against it. 273 * But at the time this test is added for Android 11, we have passed the deadline for adding 274 * the new test api, hence this approach. 275 */ assertActiveViewCountFromInlineSuggestionRenderService(int count)276 public static void assertActiveViewCountFromInlineSuggestionRenderService(int count) { 277 String response = runShellCommand( 278 "dumpsys activity service .InlineSuggestionRenderService"); 279 Log.d(TAG, "InlineSuggestionRenderService dump: " + response); 280 Pattern pattern = Pattern.compile(".*mActiveInlineSuggestions: " + count + ".*"); 281 assertWithMessage("Expecting view count " + count 282 + ", but seeing different count from service dumpsys " + response).that( 283 pattern.matcher(response).find()).isTrue(); 284 } 285 286 /** 287 * Sets whether the user completed the initial setup. 288 */ setUserComplete(boolean complete)289 public static void setUserComplete(boolean complete) { 290 sUserSettings.syncSet(USER_SETUP_COMPLETE, complete ? "1" : null); 291 } 292 dump(@onNull StringBuilder builder, @NonNull ViewNode node, @NonNull String prefix, int childId)293 private static void dump(@NonNull StringBuilder builder, @NonNull ViewNode node, 294 @NonNull String prefix, int childId) { 295 final int childrenSize = node.getChildCount(); 296 builder.append("\n").append(prefix) 297 .append("child #").append(childId).append(':'); 298 append(builder, "afId", node.getAutofillId()); 299 append(builder, "afType", node.getAutofillType()); 300 append(builder, "afValue", toString(node.getAutofillValue())); 301 append(builder, "resId", node.getIdEntry()); 302 append(builder, "class", node.getClassName()); 303 append(builder, "text", node.getText()); 304 append(builder, "webDomain", node.getWebDomain()); 305 append(builder, "checked", node.isChecked()); 306 append(builder, "focused", node.isFocused()); 307 final HtmlInfo htmlInfo = node.getHtmlInfo(); 308 if (htmlInfo != null) { 309 builder.append(", HtmlInfo[tag=").append(htmlInfo.getTag()) 310 .append(", attrs: ").append(htmlInfo.getAttributes()).append(']'); 311 } 312 if (childrenSize > 0) { 313 append(builder, "#children", childrenSize).append("\n").append(prefix); 314 prefix += " "; 315 if (childrenSize > 0) { 316 for (int i = 0; i < childrenSize; i++) { 317 dump(builder, node.getChildAt(i), prefix, i); 318 } 319 } 320 } 321 } 322 323 /** 324 * Appends a field value to a {@link StringBuilder} when it's not {@code null}. 325 */ 326 @NonNull append(@onNull StringBuilder builder, @NonNull String field, @Nullable Object value)327 public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field, 328 @Nullable Object value) { 329 if (value == null) return builder; 330 331 if ((value instanceof Boolean) && ((Boolean) value)) { 332 return builder.append(", ").append(field); 333 } 334 335 if (value instanceof Integer && ((Integer) value) == 0 336 || value instanceof CharSequence && TextUtils.isEmpty((CharSequence) value)) { 337 return builder; 338 } 339 340 return builder.append(", ").append(field).append('=').append(value); 341 } 342 343 /** 344 * Appends a field value to a {@link StringBuilder} when it's {@code true}. 345 */ 346 @NonNull append(@onNull StringBuilder builder, @NonNull String field, boolean value)347 public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field, 348 boolean value) { 349 if (value) { 350 builder.append(", ").append(field); 351 } 352 return builder; 353 } 354 355 /** 356 * Gets a node if it matches the filter criteria for the given id. 357 */ findNodeByFilter(@onNull AssistStructure structure, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)358 public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id, 359 @NonNull NodeFilter<ViewNode> filter) { 360 Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent()); 361 final int nodes = structure.getWindowNodeCount(); 362 for (int i = 0; i < nodes; i++) { 363 final WindowNode windowNode = structure.getWindowNodeAt(i); 364 final ViewNode rootNode = windowNode.getRootViewNode(); 365 final ViewNode node = findNodeByFilter(rootNode, id, filter); 366 if (node != null) { 367 return node; 368 } 369 } 370 return null; 371 } 372 373 /** 374 * Gets a node if it matches the filter criteria for the given id. 375 */ findNodeByFilter(@onNull List<FillContext> contexts, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)376 public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id, 377 @NonNull NodeFilter<ViewNode> filter) { 378 for (FillContext context : contexts) { 379 ViewNode node = findNodeByFilter(context.getStructure(), id, filter); 380 if (node != null) { 381 return node; 382 } 383 } 384 return null; 385 } 386 387 /** 388 * Gets a node if it matches the filter criteria for the given id. 389 */ findNodeByFilter(@onNull ViewNode node, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)390 public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id, 391 @NonNull NodeFilter<ViewNode> filter) { 392 if (filter.matches(node, id)) { 393 return node; 394 } 395 final int childrenSize = node.getChildCount(); 396 if (childrenSize > 0) { 397 for (int i = 0; i < childrenSize; i++) { 398 final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter); 399 if (found != null) { 400 return found; 401 } 402 } 403 } 404 return null; 405 } 406 407 /** 408 * Gets a node given its Android resource id, or {@code null} if not found. 409 */ findNodeByResourceId(AssistStructure structure, String resourceId)410 public static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) { 411 return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER); 412 } 413 414 /** 415 * Gets a node given its Android resource id, or {@code null} if not found. 416 */ findNodeByResourceId(List<FillContext> contexts, String resourceId)417 public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) { 418 return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER); 419 } 420 421 /** 422 * Gets a node given its Android resource id, or {@code null} if not found. 423 */ findNodeByResourceId(ViewNode node, String resourceId)424 public static ViewNode findNodeByResourceId(ViewNode node, String resourceId) { 425 return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER); 426 } 427 428 /** 429 * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found. 430 */ findNodeByHtmlName(AssistStructure structure, String htmlName)431 public static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) { 432 return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER); 433 } 434 435 /** 436 * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found. 437 */ findNodeByHtmlName(List<FillContext> contexts, String htmlName)438 public static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) { 439 return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER); 440 } 441 442 /** 443 * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found. 444 */ findNodeByHtmlName(ViewNode node, String htmlName)445 public static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) { 446 return findNodeByFilter(node, htmlName, HTML_NAME_FILTER); 447 } 448 449 /** 450 * Gets a node given the value of its (single) autofill hint property, or {@code null} if not 451 * found. 452 */ findNodeByAutofillHint(ViewNode node, String hint)453 public static ViewNode findNodeByAutofillHint(ViewNode node, String hint) { 454 return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER); 455 } 456 457 /** 458 * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if 459 * not found. 460 */ findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id)461 public static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) { 462 return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER); 463 } 464 465 /** 466 * Gets a node given its Android resource id. 467 */ 468 @NonNull findAutofillIdByResourceId(@onNull FillContext context, @NonNull String resourceId)469 public static AutofillId findAutofillIdByResourceId(@NonNull FillContext context, 470 @NonNull String resourceId) { 471 final ViewNode node = findNodeByFilter(context.getStructure(), resourceId, 472 RESOURCE_ID_FILTER); 473 assertWithMessage("No node for resourceId %s", resourceId).that(node).isNotNull(); 474 return node.getAutofillId(); 475 } 476 477 /** 478 * Gets the {@code name} attribute of a node representing an HTML input tag. 479 */ 480 @Nullable getHtmlName(@onNull ViewNode node)481 public static String getHtmlName(@NonNull ViewNode node) { 482 final HtmlInfo htmlInfo = node.getHtmlInfo(); 483 if (htmlInfo == null) { 484 return null; 485 } 486 final String tag = htmlInfo.getTag(); 487 if (!"input".equals(tag)) { 488 Log.w(TAG, "getHtmlName(): invalid tag (" + tag + ") on " + htmlInfo); 489 return null; 490 } 491 for (Pair<String, String> attr : htmlInfo.getAttributes()) { 492 if ("name".equals(attr.first)) { 493 return attr.second; 494 } 495 } 496 Log.w(TAG, "getHtmlName(): no 'name' attribute on " + htmlInfo); 497 return null; 498 } 499 500 /** 501 * Gets a node given its expected text, or {@code null} if not found. 502 */ findNodeByText(AssistStructure structure, String text)503 public static ViewNode findNodeByText(AssistStructure structure, String text) { 504 return findNodeByFilter(structure, text, TEXT_FILTER); 505 } 506 507 /** 508 * Gets a node given its expected text, or {@code null} if not found. 509 */ findNodeByText(ViewNode node, String text)510 public static ViewNode findNodeByText(ViewNode node, String text) { 511 return findNodeByFilter(node, text, TEXT_FILTER); 512 } 513 514 /** 515 * Gets a view that contains the an autofill hint, or {@code null} if not found. 516 */ findViewByAutofillHint(Activity activity, String hint)517 public static View findViewByAutofillHint(Activity activity, String hint) { 518 final View rootView = activity.getWindow().getDecorView().getRootView(); 519 return findViewByAutofillHint(rootView, hint); 520 } 521 522 /** 523 * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if 524 * not found. 525 */ findViewByAutofillHint(View view, String hint)526 public static View findViewByAutofillHint(View view, String hint) { 527 if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view; 528 if ((view instanceof ViewGroup)) { 529 final ViewGroup group = (ViewGroup) view; 530 for (int i = 0; i < group.getChildCount(); i++) { 531 final View child = findViewByAutofillHint(group.getChildAt(i), hint); 532 if (child != null) return child; 533 } 534 } 535 return null; 536 } 537 538 /** 539 * Asserts a text-based node is sanitized. 540 */ assertTextIsSanitized(ViewNode node)541 public static void assertTextIsSanitized(ViewNode node) { 542 final CharSequence text = node.getText(); 543 final String resourceId = node.getIdEntry(); 544 if (!TextUtils.isEmpty(text)) { 545 throw new AssertionError("text on sanitized field " + resourceId + ": " + text); 546 } 547 548 assertNotFromResources(node); 549 assertNodeHasNoAutofillValue(node); 550 } 551 assertNotFromResources(ViewNode node)552 private static void assertNotFromResources(ViewNode node) { 553 assertThat(node.getTextIdEntry()).isNull(); 554 } 555 assertNodeHasNoAutofillValue(ViewNode node)556 public static void assertNodeHasNoAutofillValue(ViewNode node) { 557 final AutofillValue value = node.getAutofillValue(); 558 if (value != null) { 559 final String text = value.isText() ? value.getTextValue().toString() : "N/A"; 560 throw new AssertionError("node has value: " + value + " text=" + text); 561 } 562 } 563 564 /** 565 * Asserts the contents of a text-based node that is also auto-fillable. 566 */ assertTextOnly(ViewNode node, String expectedValue)567 public static void assertTextOnly(ViewNode node, String expectedValue) { 568 assertText(node, expectedValue, false); 569 assertNotFromResources(node); 570 } 571 572 /** 573 * Asserts the contents of a text-based node that is also auto-fillable. 574 */ assertTextOnly(AssistStructure structure, String resourceId, String expectedValue)575 public static void assertTextOnly(AssistStructure structure, String resourceId, 576 String expectedValue) { 577 final ViewNode node = findNodeByResourceId(structure, resourceId); 578 assertText(node, expectedValue, false); 579 assertNotFromResources(node); 580 } 581 582 /** 583 * Asserts the contents of a text-based node that is also auto-fillable. 584 */ assertTextAndValue(ViewNode node, String expectedValue)585 public static void assertTextAndValue(ViewNode node, String expectedValue) { 586 assertText(node, expectedValue, true); 587 assertNotFromResources(node); 588 } 589 590 /** 591 * Asserts a text-based node exists and verify its values. 592 */ assertTextAndValue(AssistStructure structure, String resourceId, String expectedValue)593 public static ViewNode assertTextAndValue(AssistStructure structure, String resourceId, 594 String expectedValue) { 595 final ViewNode node = findNodeByResourceId(structure, resourceId); 596 assertTextAndValue(node, expectedValue); 597 return node; 598 } 599 600 /** 601 * Asserts a text-based node exists and is sanitized. 602 */ assertValue(AssistStructure structure, String resourceId, String expectedValue)603 public static ViewNode assertValue(AssistStructure structure, String resourceId, 604 String expectedValue) { 605 final ViewNode node = findNodeByResourceId(structure, resourceId); 606 assertTextValue(node, expectedValue); 607 return node; 608 } 609 610 /** 611 * Asserts the values of a text-based node whose string come from resoruces. 612 */ assertTextFromResources(AssistStructure structure, String resourceId, String expectedValue, boolean isAutofillable, String expectedTextIdEntry)613 public static ViewNode assertTextFromResources(AssistStructure structure, String resourceId, 614 String expectedValue, boolean isAutofillable, String expectedTextIdEntry) { 615 final ViewNode node = findNodeByResourceId(structure, resourceId); 616 assertText(node, expectedValue, isAutofillable); 617 assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry); 618 return node; 619 } 620 assertHintFromResources(AssistStructure structure, String resourceId, String expectedValue, String expectedHintIdEntry)621 public static ViewNode assertHintFromResources(AssistStructure structure, String resourceId, 622 String expectedValue, String expectedHintIdEntry) { 623 final ViewNode node = findNodeByResourceId(structure, resourceId); 624 assertThat(node.getHint()).isEqualTo(expectedValue); 625 assertThat(node.getHintIdEntry()).isEqualTo(expectedHintIdEntry); 626 return node; 627 } 628 assertText(ViewNode node, String expectedValue, boolean isAutofillable)629 private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) { 630 assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString()) 631 .isEqualTo(expectedValue); 632 final AutofillValue value = node.getAutofillValue(); 633 final AutofillId id = node.getAutofillId(); 634 if (isAutofillable) { 635 assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull(); 636 assertWithMessage("wrong auto-fill value on %s", id) 637 .that(value.getTextValue().toString()).isEqualTo(expectedValue); 638 } else { 639 assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull(); 640 } 641 } 642 643 /** 644 * Asserts the auto-fill value of a text-based node. 645 */ assertTextValue(ViewNode node, String expectedText)646 public static ViewNode assertTextValue(ViewNode node, String expectedText) { 647 final AutofillValue value = node.getAutofillValue(); 648 final AutofillId id = node.getAutofillId(); 649 assertWithMessage("null autofill value on %s", id).that(value).isNotNull(); 650 assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue(); 651 assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString()) 652 .isEqualTo(expectedText); 653 return node; 654 } 655 656 /** 657 * Asserts the auto-fill value of a list-based node. 658 */ assertListValue(ViewNode node, int expectedIndex)659 public static ViewNode assertListValue(ViewNode node, int expectedIndex) { 660 final AutofillValue value = node.getAutofillValue(); 661 final AutofillId id = node.getAutofillId(); 662 assertWithMessage("null autofill value on %s", id).that(value).isNotNull(); 663 assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue(); 664 assertWithMessage("wrong autofill value on %s", id).that(value.getListValue()) 665 .isEqualTo(expectedIndex); 666 return node; 667 } 668 669 /** 670 * Asserts the auto-fill value of a toggle-based node. 671 */ assertToggleValue(ViewNode node, boolean expectedToggle)672 public static void assertToggleValue(ViewNode node, boolean expectedToggle) { 673 final AutofillValue value = node.getAutofillValue(); 674 final AutofillId id = node.getAutofillId(); 675 assertWithMessage("null autofill value on %s", id).that(value).isNotNull(); 676 assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue(); 677 assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue()) 678 .isEqualTo(expectedToggle); 679 } 680 681 /** 682 * Asserts the auto-fill value of a date-based node. 683 */ assertDateValue(Object object, AutofillValue value, int year, int month, int day)684 public static void assertDateValue(Object object, AutofillValue value, int year, int month, 685 int day) { 686 assertWithMessage("null autofill value on %s", object).that(value).isNotNull(); 687 assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue(); 688 689 final Calendar cal = Calendar.getInstance(); 690 cal.setTimeInMillis(value.getDateValue()); 691 692 assertWithMessage("Wrong year on AutofillValue %s", value) 693 .that(cal.get(Calendar.YEAR)).isEqualTo(year); 694 assertWithMessage("Wrong month on AutofillValue %s", value) 695 .that(cal.get(Calendar.MONTH)).isEqualTo(month); 696 assertWithMessage("Wrong day on AutofillValue %s", value) 697 .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day); 698 } 699 700 /** 701 * Asserts the auto-fill value of a date-based node. 702 */ assertDateValue(ViewNode node, int year, int month, int day)703 public static void assertDateValue(ViewNode node, int year, int month, int day) { 704 assertDateValue(node, node.getAutofillValue(), year, month, day); 705 } 706 707 /** 708 * Asserts the auto-fill value of a date-based view. 709 */ assertDateValue(View view, int year, int month, int day)710 public static void assertDateValue(View view, int year, int month, int day) { 711 assertDateValue(view, view.getAutofillValue(), year, month, day); 712 } 713 714 /** 715 * Asserts the auto-fill value of a time-based node. 716 */ assertTimeValue(Object object, AutofillValue value, int hour, int minute)717 private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) { 718 assertWithMessage("null autofill value on %s", object).that(value).isNotNull(); 719 assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue(); 720 721 final Calendar cal = Calendar.getInstance(); 722 cal.setTimeInMillis(value.getDateValue()); 723 724 assertWithMessage("Wrong hour on AutofillValue %s", value) 725 .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour); 726 assertWithMessage("Wrong minute on AutofillValue %s", value) 727 .that(cal.get(Calendar.MINUTE)).isEqualTo(minute); 728 } 729 730 /** 731 * Asserts the auto-fill value of a time-based node. 732 */ assertTimeValue(ViewNode node, int hour, int minute)733 public static void assertTimeValue(ViewNode node, int hour, int minute) { 734 assertTimeValue(node, node.getAutofillValue(), hour, minute); 735 } 736 737 /** 738 * Asserts the auto-fill value of a time-based view. 739 */ assertTimeValue(View view, int hour, int minute)740 public static void assertTimeValue(View view, int hour, int minute) { 741 assertTimeValue(view, view.getAutofillValue(), hour, minute); 742 } 743 744 /** 745 * Asserts a text-based node exists and is sanitized. 746 */ assertTextIsSanitized(AssistStructure structure, String resourceId)747 public static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) { 748 final ViewNode node = findNodeByResourceId(structure, resourceId); 749 assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull(); 750 assertTextIsSanitized(node); 751 return node; 752 } 753 754 /** 755 * Asserts a list-based node exists and is sanitized. 756 */ assertListValueIsSanitized(AssistStructure structure, String resourceId)757 public static void assertListValueIsSanitized(AssistStructure structure, String resourceId) { 758 final ViewNode node = findNodeByResourceId(structure, resourceId); 759 assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull(); 760 assertTextIsSanitized(node); 761 } 762 763 /** 764 * Asserts a toggle node exists and is sanitized. 765 */ assertToggleIsSanitized(AssistStructure structure, String resourceId)766 public static void assertToggleIsSanitized(AssistStructure structure, String resourceId) { 767 final ViewNode node = findNodeByResourceId(structure, resourceId); 768 assertNodeHasNoAutofillValue(node); 769 assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked()) 770 .isFalse(); 771 } 772 773 /** 774 * Asserts a node exists and has the {@code expected} number of children. 775 */ assertNumberOfChildren(AssistStructure structure, String resourceId, int expected)776 public static void assertNumberOfChildren(AssistStructure structure, String resourceId, 777 int expected) { 778 final ViewNode node = findNodeByResourceId(structure, resourceId); 779 final int actual = node.getChildCount(); 780 if (actual != expected) { 781 dumpStructure("assertNumberOfChildren()", structure); 782 throw new AssertionError("assertNumberOfChildren() for " + resourceId 783 + " failed: expected " + expected + ", got " + actual); 784 } 785 } 786 787 /** 788 * Asserts the number of children in the Assist structure. 789 */ assertNumberOfChildrenWithWindowTitle(AssistStructure structure, int expected, CharSequence windowTitle)790 public static void assertNumberOfChildrenWithWindowTitle(AssistStructure structure, 791 int expected, CharSequence windowTitle) { 792 final int actual = getNumberNodes(structure, windowTitle); 793 if (actual != expected) { 794 dumpStructure("assertNumberOfChildren()", structure); 795 throw new AssertionError("assertNumberOfChildren() for structure failed: expected " 796 + expected + ", got " + actual); 797 } 798 } 799 800 /** 801 * Gets the total number of nodes in an structure. 802 * A node that has a non-null IdPackage which does not match the test package is not counted. 803 */ getNumberNodes(AssistStructure structure, CharSequence windowTitle)804 public static int getNumberNodes(AssistStructure structure, 805 CharSequence windowTitle) { 806 int count = 0; 807 final int nodes = structure.getWindowNodeCount(); 808 for (int i = 0; i < nodes; i++) { 809 final WindowNode windowNode = structure.getWindowNodeAt(i); 810 if (windowNode.getTitle().equals(windowTitle)) { 811 final ViewNode rootNode = windowNode.getRootViewNode(); 812 count += getNumberNodes(rootNode); 813 } 814 } 815 return count; 816 } 817 818 /** 819 * Gets the activity title. 820 */ getActivityTitle(Instrumentation instrumentation, Activity activity)821 public static CharSequence getActivityTitle(Instrumentation instrumentation, 822 Activity activity) { 823 final StringBuilder titleBuilder = new StringBuilder(); 824 instrumentation.runOnMainSync(() -> titleBuilder.append(activity.getTitle())); 825 return titleBuilder; 826 } 827 828 /** 829 * Gets the total number of nodes in an node, including all descendants and the node itself. 830 * A node that has a non-null IdPackage which does not match the test package is not counted. 831 */ getNumberNodes(ViewNode node)832 public static int getNumberNodes(ViewNode node) { 833 if (node.getIdPackage() != null && !node.getIdPackage().equals(MY_PACKAGE)) { 834 Log.w(TAG, "ViewNode ignored in getNumberNodes because of mismatched package: " 835 + node.getIdPackage()); 836 return 0; 837 } 838 int count = 1; 839 final int childrenSize = node.getChildCount(); 840 for (int i = 0; i < childrenSize; i++) { 841 count += getNumberNodes(node.getChildAt(i)); 842 } 843 return count; 844 } 845 846 /** 847 * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given 848 * {@code resourceIds}. 849 */ getAutofillIds(Function<String, AutofillId> autofillIdResolver, String[] resourceIds)850 public static AutofillId[] getAutofillIds(Function<String, AutofillId> autofillIdResolver, 851 String[] resourceIds) { 852 if (resourceIds == null) return null; 853 854 final AutofillId[] requiredIds = new AutofillId[resourceIds.length]; 855 for (int i = 0; i < resourceIds.length; i++) { 856 final String resourceId = resourceIds[i]; 857 requiredIds[i] = autofillIdResolver.apply(resourceId); 858 } 859 return requiredIds; 860 } 861 862 /** 863 * Prevents the screen to rotate by itself 864 */ disableAutoRotation(UiBot uiBot)865 public static void disableAutoRotation(UiBot uiBot) throws Exception { 866 runShellCommand(ACCELLEROMETER_CHANGE, 0); 867 uiBot.setScreenOrientation(PORTRAIT); 868 } 869 870 /** 871 * Allows the screen to rotate by itself 872 */ allowAutoRotation()873 public static void allowAutoRotation() { 874 runShellCommand(ACCELLEROMETER_CHANGE, 1); 875 } 876 877 /** 878 * Gets the maximum number of partitions per session. 879 */ getMaxPartitions()880 public static int getMaxPartitions() { 881 return Integer.parseInt(runShellCommand("cmd autofill get max_partitions")); 882 } 883 884 /** 885 * Sets the maximum number of partitions per session. 886 */ setMaxPartitions(int value)887 public static void setMaxPartitions(int value) throws Exception { 888 runShellCommand("cmd autofill set max_partitions %d", value); 889 SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_partitions", () -> { 890 return getMaxPartitions() == value ? Boolean.TRUE : null; 891 }); 892 } 893 894 /** 895 * Gets the maximum number of visible datasets. 896 */ getMaxVisibleDatasets()897 public static int getMaxVisibleDatasets() { 898 return Integer.parseInt(runShellCommand("cmd autofill get max_visible_datasets")); 899 } 900 901 /** 902 * Sets the maximum number of visible datasets. 903 */ setMaxVisibleDatasets(int value)904 public static void setMaxVisibleDatasets(int value) throws Exception { 905 runShellCommand("cmd autofill set max_visible_datasets %d", value); 906 SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_visible_datasets", () -> { 907 return getMaxVisibleDatasets() == value ? Boolean.TRUE : null; 908 }); 909 } 910 911 /** 912 * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi. 913 */ isAutofillWindowFullScreen(Context context)914 public static boolean isAutofillWindowFullScreen(Context context) { 915 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); 916 } 917 918 /** 919 * Checks if PCC is enabled for the device 920 */ isPccSupported(Context context)921 public static boolean isPccSupported(Context context) { 922 final PackageManager packageManager = context.getPackageManager(); 923 if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 924 Log.v(TAG, "isPccSupported(): is auto"); 925 return false; 926 } 927 if (packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) { 928 Log.v(TAG, "isPccSupported(): is PC"); 929 return false; 930 } 931 return true; 932 } 933 934 /** 935 * Returns if devices is Automotive device 936 */ isAutomotive(Context context)937 public static boolean isAutomotive(Context context) { 938 final PackageManager packageManager = context.getPackageManager(); 939 return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 940 } 941 isMainUser(Context context)942 public static boolean isMainUser(Context context) { 943 boolean mainUser = false; 944 try { 945 final UserManager userManager = context.getSystemService(UserManager.class); 946 mainUser = userManager.isMainUser(); 947 } catch (SecurityException ex) { 948 // Nothing 949 mainUser = false; 950 } 951 952 return mainUser; 953 954 } 955 956 /** 957 * Checks if screen orientation can be changed. 958 */ isRotationSupported(Context context)959 public static boolean isRotationSupported(Context context) { 960 final PackageManager packageManager = context.getPackageManager(); 961 if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 962 Log.v(TAG, "isRotationSupported(): is auto"); 963 return false; 964 } 965 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 966 Log.v(TAG, "isRotationSupported(): has leanback feature"); 967 return false; 968 } 969 if (packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) { 970 Log.v(TAG, "isRotationSupported(): is PC"); 971 return false; 972 } 973 if (!packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE) 974 || !packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)) { 975 Log.v(TAG, "isRotationSupported(): no screen orientation feature"); 976 return false; 977 } 978 return true; 979 } 980 getBoolean(Context context, String id)981 private static boolean getBoolean(Context context, String id) { 982 final Resources resources = context.getResources(); 983 final int booleanId = resources.getIdentifier(id, "bool", "android"); 984 return resources.getBoolean(booleanId); 985 } 986 987 /** 988 * Uses Shell command to get the Autofill logging level. 989 */ getLoggingLevel()990 public static String getLoggingLevel() { 991 return runShellCommand("cmd autofill get log_level"); 992 } 993 994 /** 995 * Uses Shell command to set the Autofill logging level. 996 */ setLoggingLevel(String level)997 public static void setLoggingLevel(String level) { 998 runShellCommand("cmd autofill set log_level %s", level); 999 } 1000 1001 /** 1002 * Uses Settings to enable the given autofill service for the default user, and checks the 1003 * value was properly check, throwing an exception if it was not. 1004 */ enableAutofillService(String serviceName)1005 public static void enableAutofillService(String serviceName) { 1006 if (isAutofillServiceEnabled(serviceName)) return; 1007 1008 // Sets the setting synchronously. Note that the config itself is sets synchronously but 1009 // launch of the service is asynchronous after the config is updated. 1010 sUserSettings.syncSet(AUTOFILL_SERVICE, serviceName); 1011 1012 // Waits until the service is actually enabled. 1013 try { 1014 Timeouts.CONNECTION_TIMEOUT.run("Enabling Autofill service", () -> { 1015 return isAutofillServiceEnabled(serviceName) ? serviceName : null; 1016 }); 1017 } catch (Exception e) { 1018 throw new AssertionError("Enabling Autofill service failed."); 1019 } 1020 } 1021 1022 /** 1023 * Uses Settings to disable the given autofill service for the default user, and waits until 1024 * the setting is deleted. 1025 */ disableAutofillService()1026 public static void disableAutofillService() { 1027 final String currentService = sUserSettings.get(AUTOFILL_SERVICE); 1028 if (currentService == null) { 1029 Log.v(TAG, "disableAutofillService(): already disabled"); 1030 return; 1031 } 1032 Log.v(TAG, "Disabling " + currentService); 1033 sUserSettings.syncDelete(AUTOFILL_SERVICE); 1034 } 1035 1036 /** 1037 * Checks whether the given service is set as the autofill service for the default user. 1038 */ isAutofillServiceEnabled(@onNull String serviceName)1039 public static boolean isAutofillServiceEnabled(@NonNull String serviceName) { 1040 final String actualName = getAutofillServiceName(); 1041 return serviceName.equals(actualName); 1042 } 1043 1044 /** 1045 * Gets then name of the autofill service for the default user. 1046 */ getAutofillServiceName()1047 public static String getAutofillServiceName() { 1048 return sUserSettings.get(AUTOFILL_SERVICE); 1049 } 1050 1051 /** 1052 * Asserts whether the given service is enabled as the autofill service for the default user. 1053 */ assertAutofillServiceStatus(@onNull String serviceName, boolean enabled)1054 public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) { 1055 final String actual = sUserSettings.get(AUTOFILL_SERVICE); 1056 final String expected = enabled ? serviceName : null; 1057 assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE) 1058 .that(actual).isEqualTo(expected); 1059 } 1060 1061 /** 1062 * Enables / disables the default augmented autofill service. 1063 */ setDefaultAugmentedAutofillServiceEnabled(boolean enabled)1064 public static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) { 1065 Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled); 1066 runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s", 1067 Boolean.toString(enabled)); 1068 } 1069 1070 /** 1071 * Sets the pcc detection service temporarily for 300 seconds. 1072 */ setAutofillDetectionService(String service)1073 public static void setAutofillDetectionService(String service) { 1074 Log.d(TAG, "setAutofillDetectionService"); 1075 runShellCommand("cmd autofill set temporary-detection-service 0 %s 30000", 1076 service); 1077 } 1078 1079 /** 1080 * Reset the pcc detection service 1081 */ resetAutofillDetectionService()1082 public static void resetAutofillDetectionService() { 1083 Log.d(TAG, "resetAutofillDetectionService"); 1084 runShellCommand("cmd autofill set temporary-detection-service 0"); 1085 } 1086 1087 /** 1088 * Gets the instrumentation context. 1089 */ getContext()1090 public static Context getContext() { 1091 return InstrumentationRegistry.getInstrumentation().getContext(); 1092 } 1093 1094 /** 1095 * Asserts the node has an {@code HTMLInfo} property, with the given tag. 1096 */ assertHasHtmlTag(ViewNode node, String expectedTag)1097 public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) { 1098 final HtmlInfo info = node.getHtmlInfo(); 1099 assertWithMessage("node doesn't have htmlInfo").that(info).isNotNull(); 1100 assertWithMessage("wrong tag").that(info.getTag()).isEqualTo(expectedTag); 1101 return info; 1102 } 1103 1104 /** 1105 * Gets the value of an {@code HTMLInfo} attribute. 1106 */ 1107 @Nullable getAttributeValue(HtmlInfo info, String attribute)1108 public static String getAttributeValue(HtmlInfo info, String attribute) { 1109 for (Pair<String, String> pair : info.getAttributes()) { 1110 if (pair.first.equals(attribute)) { 1111 return pair.second; 1112 } 1113 } 1114 return null; 1115 } 1116 1117 /** 1118 * Asserts a {@code HTMLInfo} has an attribute with a given value. 1119 */ assertHasAttribute(HtmlInfo info, String attribute, String expectedValue)1120 public static void assertHasAttribute(HtmlInfo info, String attribute, String expectedValue) { 1121 final String actualValue = getAttributeValue(info, attribute); 1122 assertWithMessage("Attribute %s not found", attribute).that(actualValue).isNotNull(); 1123 assertWithMessage("Wrong value for Attribute %s", attribute) 1124 .that(actualValue).isEqualTo(expectedValue); 1125 } 1126 1127 /** 1128 * Finds a {@link WebView} node given its expected form name. 1129 */ findWebViewNodeByFormName(AssistStructure structure, String formName)1130 public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) { 1131 return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER); 1132 } 1133 assertClientState(Object container, Bundle clientState, String key, String value)1134 private static void assertClientState(Object container, Bundle clientState, 1135 String key, String value) { 1136 assertWithMessage("'%s' should have client state", container) 1137 .that(clientState).isNotNull(); 1138 assertWithMessage("Wrong number of client state extras on '%s'", container) 1139 .that(clientState.keySet().size()).isEqualTo(1); 1140 assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container) 1141 .that(clientState.getString(key)).isEqualTo(value); 1142 } 1143 1144 /** 1145 * Asserts the content of a {@link FillEventHistory#getClientState()}. 1146 * 1147 * @param history event to be asserted 1148 * @param key the only key expected in the client state bundle 1149 * @param value the only value expected in the client state bundle 1150 */ 1151 @SuppressWarnings("javadoc") assertDeprecatedClientState(@onNull FillEventHistory history, @NonNull String key, @NonNull String value)1152 public static void assertDeprecatedClientState(@NonNull FillEventHistory history, 1153 @NonNull String key, @NonNull String value) { 1154 assertThat(history).isNotNull(); 1155 @SuppressWarnings("deprecation") 1156 final Bundle clientState = history.getClientState(); 1157 assertClientState(history, clientState, key, value); 1158 } 1159 1160 /** 1161 * Asserts the {@link FillEventHistory#getClientState()} is not set. 1162 * 1163 * @param history event to be asserted 1164 */ 1165 @SuppressWarnings("javadoc") assertNoDeprecatedClientState(@onNull FillEventHistory history)1166 public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) { 1167 assertThat(history).isNotNull(); 1168 @SuppressWarnings("deprecation") 1169 final Bundle clientState = history.getClientState(); 1170 assertWithMessage("History '%s' should not have client state", history) 1171 .that(clientState).isNull(); 1172 } 1173 assertFillEventPresentationType(FillEventHistory.Event event, int expectedType)1174 private static void assertFillEventPresentationType(FillEventHistory.Event event, 1175 int expectedType) { 1176 assertThat(event.getUiType()).isEqualTo(expectedType); 1177 } 1178 1179 /** 1180 * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}. 1181 * 1182 * @param event event to be asserted 1183 * @param eventType expected type 1184 * @param datasetId dataset set id expected in the event 1185 * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't 1186 * have client state) 1187 * @param value the only value expected in the client state bundle (or {@code null} if it 1188 * shouldn't have client state) 1189 * @param fieldClassificationResults expected results when asserting field classification 1190 */ assertFillEvent(@onNull FillEventHistory.Event event, int eventType, @Nullable String datasetId, @Nullable String key, @Nullable String value, @Nullable FieldClassificationResult[] fieldClassificationResults)1191 private static void assertFillEvent(@NonNull FillEventHistory.Event event, 1192 int eventType, @Nullable String datasetId, 1193 @Nullable String key, @Nullable String value, 1194 @Nullable FieldClassificationResult[] fieldClassificationResults) { 1195 assertThat(event).isNotNull(); 1196 assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType); 1197 if (datasetId == null) { 1198 assertWithMessage("Event %s should not have dataset id", event) 1199 .that(event.getDatasetId()).isNull(); 1200 } else { 1201 assertWithMessage("Wrong dataset id for %s", event) 1202 .that(event.getDatasetId()).isEqualTo(datasetId); 1203 } 1204 final Bundle clientState = event.getClientState(); 1205 if (key == null) { 1206 assertWithMessage("Event '%s' should not have client state", event) 1207 .that(clientState).isNull(); 1208 } else { 1209 assertClientState(event, clientState, key, value); 1210 } 1211 assertWithMessage("Event '%s' should not have selected datasets", event) 1212 .that(event.getSelectedDatasetIds()).isEmpty(); 1213 assertWithMessage("Event '%s' should not have ignored datasets", event) 1214 .that(event.getIgnoredDatasetIds()).isEmpty(); 1215 assertWithMessage("Event '%s' should not have changed fields", event) 1216 .that(event.getChangedFields()).isEmpty(); 1217 assertWithMessage("Event '%s' should not have manually-entered fields", event) 1218 .that(event.getManuallyEnteredField()).isEmpty(); 1219 final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification(); 1220 if (fieldClassificationResults == null) { 1221 assertThat(detectedFields).isEmpty(); 1222 } else { 1223 assertThat(detectedFields).hasSize(fieldClassificationResults.length); 1224 int i = 0; 1225 for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) { 1226 assertMatches(i, entry, fieldClassificationResults[i]); 1227 i++; 1228 } 1229 } 1230 } 1231 assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult, FieldClassificationResult expectedResult)1232 private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult, 1233 FieldClassificationResult expectedResult) { 1234 assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey()) 1235 .isEqualTo(expectedResult.id); 1236 final List<Match> matches = actualResult.getValue().getMatches(); 1237 assertWithMessage("Wrong number of matches: " + matches).that(matches.size()) 1238 .isEqualTo(expectedResult.categoryIds.length); 1239 for (int j = 0; j < matches.size(); j++) { 1240 final Match match = matches.get(j); 1241 assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match) 1242 .that(match.getCategoryId()).isEqualTo(expectedResult.categoryIds[j]); 1243 assertWithMessage("Wrong score at (%s, %s): %s", i, j, match) 1244 .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]); 1245 } 1246 } 1247 1248 /** 1249 * Asserts the content of a 1250 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event. 1251 * 1252 * @param event event to be asserted 1253 * @param datasetId dataset set id expected in the event 1254 */ assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId, int uiType)1255 public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event, 1256 @Nullable String datasetId, int uiType) { 1257 assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null); 1258 assertFillEventPresentationType(event, uiType); 1259 } 1260 1261 /** 1262 * Asserts that {@android.service.autofill.FillEventHistory.Event#TYPE_VIEW_REQUESTED_AUTOFILL} 1263 * is present in the FillEventHistory 1264 */ assertFillEventForViewEntered(@onNull FillEventHistory.Event event)1265 public static void assertFillEventForViewEntered(@NonNull FillEventHistory.Event event) { 1266 assertFillEvent(event, TYPE_VIEW_REQUESTED_AUTOFILL, null, null, null, null); 1267 } 1268 1269 /** 1270 * Asserts the content of a 1271 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event. 1272 * 1273 * @param event event to be asserted 1274 * @param datasetId dataset set id expected in the event 1275 * @param key the only key expected in the client state bundle 1276 * @param value the only value expected in the client state bundle 1277 */ assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId, @Nullable String key, @Nullable String value, int uiType)1278 public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event, 1279 @Nullable String datasetId, @Nullable String key, @Nullable String value, int uiType) { 1280 assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null); 1281 assertFillEventPresentationType(event, uiType); 1282 } 1283 1284 /** 1285 * Asserts the content of a 1286 * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event. 1287 * 1288 * @param event event to be asserted 1289 * @param datasetId dataset set id expected in the event 1290 * @param key the only key expected in the client state bundle 1291 * @param value the only value expected in the client state bundle 1292 */ assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1293 public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event, 1294 @Nullable String datasetId, @NonNull String key, @NonNull String value) { 1295 assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null); 1296 } 1297 1298 /** 1299 * Asserts the content of a 1300 * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event. 1301 * 1302 * @param event event to be asserted 1303 * @param datasetId dataset set id expected in the event 1304 */ assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @Nullable String datasetId)1305 public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event, 1306 @Nullable String datasetId) { 1307 assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null); 1308 } 1309 1310 /** 1311 * Asserts the content of a 1312 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event. 1313 * 1314 * @param event event to be asserted 1315 * @param key the only key expected in the client state bundle 1316 * @param value the only value expected in the client state bundle 1317 * @param uiType the expected ui presentation type 1318 */ assertFillEventForDatasetShown(@onNull FillEventHistory.Event event, @NonNull String key, @NonNull String value, int uiType)1319 public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event, 1320 @NonNull String key, @NonNull String value, int uiType) { 1321 assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null); 1322 assertFillEventPresentationType(event, uiType); 1323 } 1324 1325 /** 1326 * Asserts the content of a 1327 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event. 1328 * 1329 * @param event event to be asserted 1330 */ assertFillEventForDatasetShown(@onNull FillEventHistory.Event event, int uiType)1331 public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event, 1332 int uiType) { 1333 assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null); 1334 assertFillEventPresentationType(event, uiType); 1335 } 1336 1337 /** 1338 * Asserts the content of a 1339 * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED} 1340 * event. 1341 * 1342 * @param event event to be asserted 1343 * @param datasetId dataset set id expected in the event 1344 * @param key the only key expected in the client state bundle 1345 * @param value the only value expected in the client state bundle 1346 * @param uiType the expected ui presentation type 1347 */ assertFillEventForDatasetAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType)1348 public static void assertFillEventForDatasetAuthenticationSelected( 1349 @NonNull FillEventHistory.Event event, 1350 @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType) { 1351 assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null); 1352 assertFillEventPresentationType(event, uiType); 1353 } 1354 1355 /** 1356 * Asserts the content of a 1357 * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event. 1358 * 1359 * @param event event to be asserted 1360 * @param datasetId dataset set id expected in the event 1361 * @param key the only key expected in the client state bundle 1362 * @param value the only value expected in the client state bundle 1363 * @param uiType the expected ui presentation type 1364 */ assertFillEventForAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType)1365 public static void assertFillEventForAuthenticationSelected( 1366 @NonNull FillEventHistory.Event event, 1367 @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType) { 1368 assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null); 1369 assertFillEventPresentationType(event, uiType); 1370 } 1371 assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull AutofillId fieldId, @NonNull String categoryId, float score)1372 public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event, 1373 @NonNull AutofillId fieldId, @NonNull String categoryId, float score) { 1374 assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, 1375 new FieldClassificationResult[] { 1376 new FieldClassificationResult(fieldId, categoryId, score) 1377 }); 1378 } 1379 assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull FieldClassificationResult[] results)1380 public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event, 1381 @NonNull FieldClassificationResult[] results) { 1382 assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results); 1383 } 1384 assertFillEventForContextCommitted(@onNull FillEventHistory.Event event)1385 public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) { 1386 assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null); 1387 } 1388 assertShownAndSelectedHaveSameFocusedId( @onNull FillEventHistory.Event shownEvent, @NonNull FillEventHistory.Event selectionEvent)1389 public static void assertShownAndSelectedHaveSameFocusedId( 1390 @NonNull FillEventHistory.Event shownEvent, 1391 @NonNull FillEventHistory.Event selectionEvent) { 1392 assertWithMessage("Not a shown event") 1393 .that(shownEvent.getType()) 1394 .isEqualTo(TYPE_DATASETS_SHOWN); 1395 assertWithMessage("Not a selection event") 1396 .that(selectionEvent.getType()) 1397 .isEqualTo(TYPE_DATASET_AUTHENTICATION_SELECTED); 1398 assertWithMessage("Shown event has no focused id") 1399 .that(shownEvent.getFocusedId()) 1400 .isNotNull(); 1401 assertWithMessage("Selection event has no focused id") 1402 .that(selectionEvent.getFocusedId()) 1403 .isNotNull(); 1404 assertWithMessage("Shown and selected Events don't have the same focused id") 1405 .that(shownEvent.getFocusedId()) 1406 .isEqualTo(selectionEvent.getFocusedId()); 1407 } 1408 assertShownAndSelectedHaveDifferentFocusedId( @onNull FillEventHistory.Event shownEvent, @NonNull FillEventHistory.Event selectionEvent)1409 public static void assertShownAndSelectedHaveDifferentFocusedId( 1410 @NonNull FillEventHistory.Event shownEvent, 1411 @NonNull FillEventHistory.Event selectionEvent) { 1412 assertWithMessage("Not a shown event") 1413 .that(shownEvent.getType()) 1414 .isEqualTo(TYPE_DATASETS_SHOWN); 1415 assertWithMessage("Not a selection event") 1416 .that(selectionEvent.getType()) 1417 .isEqualTo(TYPE_DATASET_AUTHENTICATION_SELECTED); 1418 assertWithMessage("Shown event has no focused id") 1419 .that(shownEvent.getFocusedId()) 1420 .isNotNull(); 1421 assertWithMessage("Selection event has no focused id") 1422 .that(selectionEvent.getFocusedId()) 1423 .isNotNull(); 1424 assertWithMessage("Shown and selected Events don't have the same focused id") 1425 .that(shownEvent.getFocusedId()) 1426 .isNotEqualTo(selectionEvent.getFocusedId()); 1427 } 1428 assertShownAndViewEnteredHaveSameFocusedId( @onNull FillEventHistory.Event shownEvent, @NonNull FillEventHistory.Event viewEnteredEvent)1429 public static void assertShownAndViewEnteredHaveSameFocusedId( 1430 @NonNull FillEventHistory.Event shownEvent, 1431 @NonNull FillEventHistory.Event viewEnteredEvent) { 1432 assertWithMessage("Not a shown event") 1433 .that(shownEvent.getType()) 1434 .isEqualTo(TYPE_DATASETS_SHOWN); 1435 assertWithMessage("Not a notify view entered event") 1436 .that(viewEnteredEvent.getType()) 1437 .isEqualTo(TYPE_VIEW_REQUESTED_AUTOFILL); 1438 assertWithMessage("Shown event has no focused id") 1439 .that(shownEvent.getFocusedId()) 1440 .isNotNull(); 1441 assertWithMessage("Notify view entered event has no focused id") 1442 .that(viewEnteredEvent.getFocusedId()) 1443 .isNotNull(); 1444 assertWithMessage("Shown and notify view entered Events don't have the same focused id") 1445 .that(shownEvent.getFocusedId()) 1446 .isEqualTo(viewEnteredEvent.getFocusedId()); 1447 } 1448 1449 @NonNull getActivityName(List<FillContext> contexts)1450 public static String getActivityName(List<FillContext> contexts) { 1451 if (contexts == null) return "N/A (null contexts)"; 1452 1453 if (contexts.isEmpty()) return "N/A (empty contexts)"; 1454 1455 final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure(); 1456 if (structure == null) return "N/A (no AssistStructure)"; 1457 1458 final ComponentName componentName = structure.getActivityComponent(); 1459 if (componentName == null) return "N/A (no component name)"; 1460 1461 return componentName.flattenToShortString(); 1462 } 1463 assertFloat(float actualValue, float expectedValue)1464 public static void assertFloat(float actualValue, float expectedValue) { 1465 assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue); 1466 } 1467 assertHasFlags(int actualFlags, int expectedFlags)1468 public static void assertHasFlags(int actualFlags, int expectedFlags) { 1469 assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags) 1470 .that(actualFlags & expectedFlags).isEqualTo(expectedFlags); 1471 } 1472 assertNoFlags(int actualFlags, int expectedFlags)1473 public static void assertNoFlags(int actualFlags, int expectedFlags) { 1474 assertWithMessage("Flags %s in %s", expectedFlags, actualFlags) 1475 .that(actualFlags & expectedFlags).isEqualTo(0); 1476 } 1477 callbackEventAsString(int event)1478 public static String callbackEventAsString(int event) { 1479 switch (event) { 1480 case AutofillCallback.EVENT_INPUT_HIDDEN: 1481 return "HIDDEN"; 1482 case AutofillCallback.EVENT_INPUT_SHOWN: 1483 return "SHOWN"; 1484 case AutofillCallback.EVENT_INPUT_UNAVAILABLE: 1485 return "UNAVAILABLE"; 1486 default: 1487 return "UNKNOWN:" + event; 1488 } 1489 } 1490 importantForAutofillAsString(int mode)1491 public static String importantForAutofillAsString(int mode) { 1492 switch (mode) { 1493 case View.IMPORTANT_FOR_AUTOFILL_AUTO: 1494 return "IMPORTANT_FOR_AUTOFILL_AUTO"; 1495 case View.IMPORTANT_FOR_AUTOFILL_YES: 1496 return "IMPORTANT_FOR_AUTOFILL_YES"; 1497 case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS: 1498 return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS"; 1499 case View.IMPORTANT_FOR_AUTOFILL_NO: 1500 return "IMPORTANT_FOR_AUTOFILL_NO"; 1501 case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS: 1502 return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS"; 1503 default: 1504 return "UNKNOWN:" + mode; 1505 } 1506 } 1507 hasHint(@ullable String[] hints, @Nullable Object expectedHint)1508 public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) { 1509 if (hints == null || expectedHint == null) return false; 1510 for (String actualHint : hints) { 1511 if (expectedHint.equals(actualHint)) return true; 1512 } 1513 return false; 1514 } 1515 newClientState(String key, String value)1516 public static Bundle newClientState(String key, String value) { 1517 final Bundle clientState = new Bundle(); 1518 clientState.putString(key, value); 1519 return clientState; 1520 } 1521 assertAuthenticationClientState(String where, Bundle data, String expectedKey, String expectedValue)1522 public static void assertAuthenticationClientState(String where, Bundle data, 1523 String expectedKey, String expectedValue) { 1524 assertWithMessage("no client state on %s", where).that(data).isNotNull(); 1525 final String extraValue = data.getString(expectedKey); 1526 assertWithMessage("invalid value for %s on %s", expectedKey, where) 1527 .that(extraValue).isEqualTo(expectedValue); 1528 } 1529 1530 /** 1531 * Asserts that 2 bitmaps have are the same. If they aren't throws an exception and dump them 1532 * locally so their can be visually inspected. 1533 * 1534 * @param filename base name of the files generated in case of error 1535 * @param bitmap1 first bitmap to be compared 1536 * @param bitmap2 second bitmap to be compared 1537 */ 1538 // TODO: move to common code assertBitmapsAreSame(@onNull String filename, @Nullable Bitmap bitmap1, @Nullable Bitmap bitmap2)1539 public static void assertBitmapsAreSame(@NonNull String filename, @Nullable Bitmap bitmap1, 1540 @Nullable Bitmap bitmap2) throws IOException { 1541 assertWithMessage("1st bitmap is null").that(bitmap1).isNotNull(); 1542 assertWithMessage("2nd bitmap is null").that(bitmap2).isNotNull(); 1543 final boolean same = bitmap1.sameAs(bitmap2); 1544 if (same) { 1545 Log.v(TAG, "bitmap comparison passed for " + filename); 1546 return; 1547 } 1548 1549 final File dir = getLocalDirectory(); 1550 if (dir == null) { 1551 throw new AssertionError("bitmap comparison failed for " + filename 1552 + ", and bitmaps could not be dumped on " + dir); 1553 } 1554 final File dump1 = dumpBitmap(bitmap1, dir, filename + "-1.png"); 1555 final File dump2 = dumpBitmap(bitmap2, dir, filename + "-2.png"); 1556 throw new AssertionError( 1557 "bitmap comparison failed; check contents of " + dump1 + " and " + dump2); 1558 } 1559 1560 @Nullable getLocalDirectory()1561 private static File getLocalDirectory() { 1562 final File dir = new File(LOCAL_DIRECTORY); 1563 dir.mkdirs(); 1564 if (!dir.exists()) { 1565 Log.e(TAG, "Could not create directory " + dir); 1566 return null; 1567 } 1568 return dir; 1569 } 1570 1571 @Nullable createFile(@onNull File dir, @NonNull String filename)1572 private static File createFile(@NonNull File dir, @NonNull String filename) throws IOException { 1573 final File file = new File(dir, filename); 1574 if (file.exists()) { 1575 Log.v(TAG, "Deleting file " + file); 1576 file.delete(); 1577 } 1578 if (!file.createNewFile()) { 1579 Log.e(TAG, "Could not create file " + file); 1580 return null; 1581 } 1582 return file; 1583 } 1584 1585 @Nullable dumpBitmap(@onNull Bitmap bitmap, @NonNull File dir, @NonNull String filename)1586 private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir, 1587 @NonNull String filename) throws IOException { 1588 final File file = createFile(dir, filename); 1589 if (file != null) { 1590 dumpBitmap(bitmap, file); 1591 1592 } 1593 return file; 1594 } 1595 1596 @Nullable dumpBitmap(@onNull Bitmap bitmap, @NonNull File file)1597 public static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File file) { 1598 Log.i(TAG, "Dumping bitmap at " + file); 1599 BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName()); 1600 return file; 1601 } 1602 1603 /** 1604 * Creates a file in the device, using the name of the current test as a prefix. 1605 */ 1606 @Nullable createTestFile(@onNull String name)1607 public static File createTestFile(@NonNull String name) throws IOException { 1608 final File dir = getLocalDirectory(); 1609 if (dir == null) return null; 1610 1611 final String prefix = TestNameUtils.getCurrentTestName().replaceAll("\\.|\\(|\\/", "_") 1612 .replaceAll("\\)", ""); 1613 final String filename = prefix + "-" + name; 1614 1615 return createFile(dir, filename); 1616 } 1617 1618 /** 1619 * Offers an object to a queue or times out. 1620 * 1621 * @return {@code true} if the offer was accepted, {$code false} if it timed out or was 1622 * interrupted. 1623 */ offer(BlockingQueue<T> queue, T obj, long timeoutMs)1624 public static <T> boolean offer(BlockingQueue<T> queue, T obj, long timeoutMs) { 1625 boolean offered = false; 1626 try { 1627 offered = queue.offer(obj, timeoutMs, TimeUnit.MILLISECONDS); 1628 } catch (InterruptedException e) { 1629 Log.w(TAG, "interrupted offering", e); 1630 Thread.currentThread().interrupt(); 1631 } 1632 if (!offered) { 1633 Log.e(TAG, "could not offer " + obj + " in " + timeoutMs + "ms"); 1634 } 1635 return offered; 1636 } 1637 1638 /** 1639 * Calls this method to assert given {@code string} is equal to {@link #LARGE_STRING}, as 1640 * comparing its value using standard assertions might ANR. 1641 */ assertEqualsToLargeString(@onNull String string)1642 public static void assertEqualsToLargeString(@NonNull String string) { 1643 assertThat(string).isNotNull(); 1644 assertThat(string).hasLength(LARGE_STRING_SIZE); 1645 assertThat(string.charAt(0)).isEqualTo(LARGE_STRING_CHAR); 1646 assertThat(string.charAt(LARGE_STRING_SIZE - 1)).isEqualTo(LARGE_STRING_CHAR); 1647 } 1648 1649 /** 1650 * Asserts that autofill is enabled in the context, retrying if necessariy. 1651 */ assertAutofillEnabled(@onNull Context context, boolean expected)1652 public static void assertAutofillEnabled(@NonNull Context context, boolean expected) 1653 throws Exception { 1654 assertAutofillEnabled(context.getSystemService(AutofillManager.class), expected); 1655 } 1656 1657 /** 1658 * Asserts that autofill is enabled in the manager, retrying if necessariy. 1659 */ assertAutofillEnabled(@onNull AutofillManager afm, boolean expected)1660 public static void assertAutofillEnabled(@NonNull AutofillManager afm, boolean expected) 1661 throws Exception { 1662 Timeouts.IDLE_UNBIND_TIMEOUT.run("assertEnabled(" + expected + ")", () -> { 1663 final boolean actual = afm.isEnabled(); 1664 Log.v(TAG, "assertEnabled(): expected=" + expected + ", actual=" + actual); 1665 return actual == expected ? "not_used" : null; 1666 }); 1667 } 1668 1669 /** 1670 * Asserts these autofill ids are the same, except for the session. 1671 */ assertEqualsIgnoreSession(@onNull AutofillId id1, @NonNull AutofillId id2)1672 public static void assertEqualsIgnoreSession(@NonNull AutofillId id1, @NonNull AutofillId id2) { 1673 assertWithMessage("id1 is null").that(id1).isNotNull(); 1674 assertWithMessage("id2 is null").that(id2).isNotNull(); 1675 assertWithMessage("%s is not equal to %s", id1, id2).that(id1.equalsIgnoreSession(id2)) 1676 .isTrue(); 1677 } 1678 1679 /** 1680 * Asserts {@link View#isAutofilled()} state of the given view, waiting if necessarity to avoid 1681 * race conditions. 1682 */ assertViewAutofillState(@onNull View view, boolean expected)1683 public static void assertViewAutofillState(@NonNull View view, boolean expected) 1684 throws Exception { 1685 Timeouts.FILL_TIMEOUT.run("assertViewAutofillState(" + view + ", " + expected + ")", 1686 () -> { 1687 final boolean actual = view.isAutofilled(); 1688 Log.v(TAG, "assertViewAutofillState(): expected=" + expected + ", actual=" 1689 + actual); 1690 return actual == expected ? "not_used" : null; 1691 }); 1692 } 1693 1694 /** 1695 * Allows the test to draw overlaid windows. 1696 * 1697 * <p>Should call {@link #disallowOverlays()} afterwards. 1698 */ allowOverlays()1699 public static void allowOverlays() { 1700 ShellUtils.setOverlayPermissions(MY_PACKAGE, true); 1701 } 1702 1703 /** 1704 * Disallow the test to draw overlaid windows. 1705 * 1706 * <p>Should call {@link #disallowOverlays()} afterwards. 1707 */ disallowOverlays()1708 public static void disallowOverlays() { 1709 ShellUtils.setOverlayPermissions(MY_PACKAGE, false); 1710 } 1711 createPresentation(String message)1712 public static RemoteViews createPresentation(String message) { 1713 final RemoteViews presentation = new RemoteViews(getContext() 1714 .getPackageName(), R.layout.list_item); 1715 presentation.setTextViewText(R.id.text1, message); 1716 return presentation; 1717 } 1718 createInlinePresentation(String message)1719 public static InlinePresentation createInlinePresentation(String message) { 1720 final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 1721 PendingIntent.FLAG_IMMUTABLE); 1722 return createInlinePresentation(message, dummyIntent, false); 1723 } 1724 createInlinePresentation(String message, PendingIntent attribution)1725 public static InlinePresentation createInlinePresentation(String message, 1726 PendingIntent attribution) { 1727 return createInlinePresentation(message, attribution, false); 1728 } 1729 createPinnedInlinePresentation(String message)1730 public static InlinePresentation createPinnedInlinePresentation(String message) { 1731 final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 1732 PendingIntent.FLAG_IMMUTABLE); 1733 return createInlinePresentation(message, dummyIntent, true); 1734 } 1735 createInlinePresentation(@onNull String message, @NonNull PendingIntent attribution, boolean pinned)1736 private static InlinePresentation createInlinePresentation(@NonNull String message, 1737 @NonNull PendingIntent attribution, boolean pinned) { 1738 return new InlinePresentation( 1739 InlineSuggestionUi.newContentBuilder(attribution) 1740 .setTitle(message).build().getSlice(), 1741 new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100)) 1742 .build(), /* pinned= */ pinned); 1743 } 1744 createInlineTooltipPresentation( @onNull String message)1745 public static InlinePresentation createInlineTooltipPresentation( 1746 @NonNull String message) { 1747 final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 1748 PendingIntent.FLAG_IMMUTABLE); 1749 return createInlineTooltipPresentation(message, dummyIntent); 1750 } 1751 createInlineTooltipPresentation( @onNull String message, @NonNull PendingIntent attribution)1752 private static InlinePresentation createInlineTooltipPresentation( 1753 @NonNull String message, @NonNull PendingIntent attribution) { 1754 return InlinePresentation.createTooltipPresentation( 1755 InlineSuggestionUi.newContentBuilder(attribution) 1756 .setTitle(message).build().getSlice(), 1757 new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100)) 1758 .build()); 1759 } 1760 mockSwitchInputMethod(@onNull Context context)1761 public static void mockSwitchInputMethod(@NonNull Context context) throws Exception { 1762 final ContentResolver cr = context.getContentResolver(); 1763 final int subtype = Settings.Secure.getInt(cr, SELECTED_INPUT_METHOD_SUBTYPE); 1764 Settings.Secure.putInt(cr, SELECTED_INPUT_METHOD_SUBTYPE, subtype); 1765 } 1766 1767 /** 1768 * Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist. 1769 */ resetApplicationAutofillOptions(@onNull Context context)1770 public static void resetApplicationAutofillOptions(@NonNull Context context) { 1771 AutofillOptions options = AutofillOptions.forWhitelistingItself(); 1772 options.augmentedAutofillEnabled = false; 1773 context.getApplicationContext().setAutofillOptions(options); 1774 } 1775 1776 /** 1777 * Clear AutofillOptions. 1778 */ clearApplicationAutofillOptions(@onNull Context context)1779 public static void clearApplicationAutofillOptions(@NonNull Context context) { 1780 context.getApplicationContext().setAutofillOptions(null); 1781 } 1782 1783 /** 1784 * Set device config to set flag values. 1785 */ setDeviceConfig( @onNull Context context, @NonNull String feature, boolean value)1786 public static void setDeviceConfig( 1787 @NonNull Context context, @NonNull String feature, boolean value) { 1788 DeviceConfigStateManager deviceConfigStateManager = 1789 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, feature); 1790 setDeviceConfig(deviceConfigStateManager, String.valueOf(value)); 1791 } 1792 1793 /** 1794 * Enable fill dialog feature 1795 */ enableFillDialogFeature(@onNull Context context)1796 public static void enableFillDialogFeature(@NonNull Context context) { 1797 DeviceConfigStateManager deviceConfigStateManager = 1798 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, 1799 AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED); 1800 setDeviceConfig(deviceConfigStateManager, "true"); 1801 } 1802 1803 /** 1804 * Enable fill dialog feature 1805 */ disableFillDialogFeature(@onNull Context context)1806 public static void disableFillDialogFeature(@NonNull Context context) { 1807 DeviceConfigStateManager deviceConfigStateManager = 1808 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, 1809 AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED); 1810 setDeviceConfig(deviceConfigStateManager, "false"); 1811 } 1812 1813 /** 1814 * Enable fill dialog improvements 1815 */ enableFillDialogImprovements(@onNull Context context)1816 public static void enableFillDialogImprovements(@NonNull Context context) { 1817 enableFillDialogFeature(context); 1818 DeviceConfigStateManager deviceConfigStateManager = 1819 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, 1820 "improve_fill_dialog"); 1821 setDeviceConfig(deviceConfigStateManager, "true"); 1822 } 1823 1824 /** 1825 * Disable fill dialog improvements 1826 */ disableFillDialogImprovements(@onNull Context context)1827 public static void disableFillDialogImprovements(@NonNull Context context) { 1828 disableFillDialogFeature(context); 1829 DeviceConfigStateManager deviceConfigStateManager = 1830 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, 1831 "improve_fill_dialog"); 1832 setDeviceConfig(deviceConfigStateManager, "false"); 1833 } 1834 1835 /** 1836 * Enable PCC Detection Feature Hints 1837 */ enablePccDetectionFeature(@onNull Context context, String...types)1838 public static void enablePccDetectionFeature(@NonNull Context context, String...types) { 1839 DeviceConfigStateManager deviceConfigStateManager = 1840 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, 1841 AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS); 1842 setDeviceConfig(deviceConfigStateManager, TextUtils.join(",", types)); 1843 1844 DeviceConfigStateManager deviceConfigStateManager2 = 1845 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, 1846 AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED); 1847 setDeviceConfig(deviceConfigStateManager2, "true"); 1848 } 1849 1850 /** 1851 * Enable PCC Detection Feature Hints 1852 */ preferPccDetectionOverProvider(@onNull Context context, boolean preferPcc)1853 public static void preferPccDetectionOverProvider(@NonNull Context context, boolean preferPcc) { 1854 DeviceConfigStateManager deviceConfigStateManager = 1855 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, 1856 "prefer_provider_over_pcc"); 1857 setDeviceConfig(deviceConfigStateManager, String.valueOf(!preferPcc)); 1858 } 1859 1860 /** 1861 * Disable PCC Detection Feature 1862 */ disablePccDetectionFeature(@onNull Context context)1863 public static void disablePccDetectionFeature(@NonNull Context context) { 1864 DeviceConfigStateManager deviceConfigStateManager2 = 1865 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, 1866 AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED); 1867 setDeviceConfig(deviceConfigStateManager2, "false"); 1868 } 1869 1870 /** 1871 * Set hints list for fill dialog 1872 */ setFillDialogHints(@onNull Context context, @Nullable String hints)1873 public static void setFillDialogHints(@NonNull Context context, @Nullable String hints) { 1874 DeviceConfigStateManager deviceConfigStateManager = 1875 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, 1876 DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS); 1877 setDeviceConfig(deviceConfigStateManager, hints); 1878 } 1879 1880 /** 1881 * Disable relayout fix 1882 */ disableRelayoutFix(@onNull Context context)1883 public static void disableRelayoutFix(@NonNull Context context) { 1884 DeviceConfigStateManager deviceConfigStateManager = new DeviceConfigStateManager( 1885 context, DeviceConfig.NAMESPACE_AUTOFILL, "enable_relayout"); 1886 setDeviceConfig(deviceConfigStateManager, "false"); 1887 } 1888 1889 /** 1890 * Enable relayout fix 1891 */ enableRelayoutFix(@onNull Context context)1892 public static void enableRelayoutFix(@NonNull Context context) { 1893 DeviceConfigStateManager deviceConfigStateManager = new DeviceConfigStateManager( 1894 context, DeviceConfig.NAMESPACE_AUTOFILL, "enable_relayout"); 1895 setDeviceConfig(deviceConfigStateManager, "true"); 1896 } 1897 setDeviceConfig(@onNull DeviceConfigStateManager deviceConfigStateManager, @Nullable String value)1898 public static void setDeviceConfig(@NonNull DeviceConfigStateManager deviceConfigStateManager, 1899 @Nullable String value) { 1900 final String previousValue = deviceConfigStateManager.get(); 1901 if (TextUtils.isEmpty(value) && TextUtils.isEmpty(previousValue) 1902 || TextUtils.equals(previousValue, value)) { 1903 Log.v(TAG, "No changed in config: " + deviceConfigStateManager); 1904 return; 1905 } 1906 1907 deviceConfigStateManager.set(value); 1908 } 1909 isPccFieldClassificationSet(@onNull Context context)1910 public static boolean isPccFieldClassificationSet(@NonNull Context context) { 1911 return Boolean.valueOf(runShellCommand( 1912 "cmd autofill get field-detection-service-enabled " + context.getUserId())); 1913 } 1914 1915 /** 1916 * Whether IME is showing 1917 */ isImeShowing(WindowInsets rootWindowInsets)1918 public static boolean isImeShowing(WindowInsets rootWindowInsets) { 1919 if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) { 1920 return true; 1921 } 1922 return false; 1923 } 1924 getSystemResourceId(String id, String type, String packageName)1925 public static int getSystemResourceId(String id, String type, String packageName) { 1926 return Resources.getSystem().getIdentifier(id, type, packageName); 1927 } 1928 1929 /** 1930 * Asserts whether mock IME is showing 1931 */ assertMockImeStatus(AbstractAutoFillActivity activity, boolean expectedImeShow)1932 public static void assertMockImeStatus(AbstractAutoFillActivity activity, 1933 boolean expectedImeShow) throws Exception { 1934 Timeouts.MOCK_IME_TIMEOUT.run("assertMockImeStatus(" + expectedImeShow + ")", 1935 () -> { 1936 final boolean actual = isImeShowing(activity.getRootWindowInsets()); 1937 Log.v(TAG, "assertMockImeStatus(): expected=" + expectedImeShow + ", actual=" 1938 + actual); 1939 return actual == expectedImeShow ? "expected" : null; 1940 }); 1941 } 1942 1943 /** 1944 * Make sure the activity that the name is clazz resumed. 1945 */ assertActivityShownInBackground(Class<?> clazz)1946 public static void assertActivityShownInBackground(Class<?> clazz) throws Exception { 1947 Timeouts.UI_TIMEOUT.run("activity is not resumed: " + clazz, () -> { 1948 ArrayList<Boolean> result = new ArrayList<>(); 1949 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 1950 final Collection<Activity> stage = ActivityLifecycleMonitorRegistry.getInstance() 1951 .getActivitiesInStage(Stage.RESUMED); 1952 for (Activity act : stage) { 1953 if (act.getClass().equals(clazz)) { 1954 result.add(Boolean.TRUE); 1955 } 1956 } 1957 }); 1958 return result.isEmpty() ? null : Boolean.TRUE; 1959 }); 1960 } 1961 1962 /** 1963 * Whether the device is TV. 1964 * @param context 1965 * @return true if the device is TV, false otherwise 1966 */ isTv(Context context)1967 public static boolean isTv(Context context) { 1968 PackageManager pm = context.getPackageManager(); 1969 return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK) 1970 || pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION); 1971 } 1972 Helper()1973 private Helper() { 1974 throw new UnsupportedOperationException("contain static methods only"); 1975 } 1976 1977 public enum DeviceStateEnum { 1978 HALF_FOLDED, 1979 OPENED, 1980 REAR_DISPLAY 1981 }; 1982 1983 /** 1984 * Test if the device is in half-folded or rear display state. 1985 */ 1986 private static final class DeviceStateAssessor implements DeviceStateCallback { 1987 DeviceStateManager mDeviceStateManager; 1988 int[] mHalfFoldedStates; 1989 int[] mRearDisplayStates; 1990 int[] mOpenDisplayStates; 1991 int mCurrentState = -1; 1992 DeviceStateAssessor(Context context)1993 DeviceStateAssessor(Context context) { 1994 Resources systemRes = Resources.getSystem(); 1995 mHalfFoldedStates = getStatesFromConfig(systemRes, "config_halfFoldedDeviceStates"); 1996 mRearDisplayStates = getStatesFromConfig(systemRes, "config_rearDisplayDeviceStates"); 1997 mOpenDisplayStates = getStatesFromConfig(systemRes, "config_openDeviceStates"); 1998 try { 1999 mDeviceStateManager = context.getSystemService(DeviceStateManager.class); 2000 mDeviceStateManager.registerCallback(context.getMainExecutor(), this); 2001 Log.v(TAG, "DeviceStateAssessor initialized halfFoldedStates.length=" 2002 + mHalfFoldedStates.length + ", readDisplayStates.length=" 2003 + mRearDisplayStates.length); 2004 } catch (java.lang.IllegalStateException e) { 2005 Log.v(TAG, "DeviceStateManager not available: cannot check for half-fold"); 2006 } 2007 } 2008 getStatesFromConfig(Resources systemRes, String configKey)2009 private int[] getStatesFromConfig(Resources systemRes, String configKey) { 2010 int statesArrayIdentifier = systemRes.getIdentifier(configKey, "array", "android"); 2011 if (statesArrayIdentifier == 0) { 2012 return new int[0]; 2013 } else { 2014 return systemRes.getIntArray(statesArrayIdentifier); 2015 } 2016 } 2017 2018 @Override onDeviceStateChanged(DeviceState state)2019 public void onDeviceStateChanged(DeviceState state) { 2020 synchronized (this) { 2021 mCurrentState = state.getIdentifier(); 2022 this.notify(); 2023 } 2024 } 2025 close()2026 void close() { 2027 if (mDeviceStateManager != null) { 2028 mDeviceStateManager.unregisterCallback(this); 2029 } 2030 } 2031 isDeviceInState(DeviceStateEnum deviceState)2032 boolean isDeviceInState(DeviceStateEnum deviceState) throws InterruptedException { 2033 int[] states; 2034 switch(deviceState) { 2035 case HALF_FOLDED: 2036 states = mHalfFoldedStates; 2037 break; 2038 case OPENED: 2039 states = mOpenDisplayStates; 2040 break; 2041 case REAR_DISPLAY: 2042 states = mRearDisplayStates; 2043 break; 2044 default: 2045 return false; 2046 } 2047 if (states.length == 0 || mDeviceStateManager == null) { 2048 return false; 2049 } 2050 synchronized (this) { 2051 if (mCurrentState == -1) { 2052 this.wait(1000); 2053 } 2054 } 2055 if (mCurrentState == -1) { 2056 Log.w(TAG, "DeviceStateCallback not called within 1 second"); 2057 } 2058 Log.v(TAG, "Current state=" + mCurrentState + ", states[0]=" 2059 + states[0]); 2060 return Arrays.stream(states).anyMatch(x -> x == mCurrentState); 2061 } 2062 } 2063 isDeviceInState(Context context, DeviceStateEnum deviceState)2064 public static boolean isDeviceInState(Context context, DeviceStateEnum deviceState) { 2065 DeviceStateAssessor deviceStateAssessor = new DeviceStateAssessor(context); 2066 try { 2067 return deviceStateAssessor.isDeviceInState(deviceState); 2068 } catch (InterruptedException e) { 2069 return false; 2070 } finally { 2071 deviceStateAssessor.close(); 2072 } 2073 } 2074 2075 public static class CustomDescriptionUtils { newTemplate(String packageName)2076 public static RemoteViews newTemplate(String packageName) { 2077 return new RemoteViews(packageName, R.layout.custom_description_with_link); 2078 } 2079 newCustomDescriptionBuilder( Context context, Class<? extends Activity> activityClass, String packageName)2080 public static final CustomDescription.Builder newCustomDescriptionBuilder( 2081 Context context, Class<? extends Activity> activityClass, String packageName) { 2082 final Intent intent = new Intent(context, activityClass); 2083 intent.setFlags( 2084 Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 2085 return newCustomDescriptionBuilder(context, intent, packageName); 2086 } 2087 newCustomDescription( Context context, Class<? extends Activity> activityClass, String packageName)2088 public static final CustomDescription newCustomDescription( 2089 Context context, Class<? extends Activity> activityClass, String packageName) { 2090 return newCustomDescriptionBuilder(context, activityClass, packageName).build(); 2091 } 2092 newCustomDescriptionBuilder( Context context, Intent intent, String packageName)2093 public static final CustomDescription.Builder newCustomDescriptionBuilder( 2094 Context context, Intent intent, String packageName) { 2095 final RemoteViews presentation = newTemplate(packageName); 2096 final PendingIntent pendingIntent = 2097 PendingIntent.getActivity( 2098 context, 2099 0, 2100 intent, 2101 PendingIntent.FLAG_MUTABLE, 2102 ActivityOptions.makeBasic() 2103 .setPendingIntentCreatorBackgroundActivityStartMode( 2104 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 2105 .toBundle()); 2106 presentation.setOnClickPendingIntent(R.id.link, pendingIntent); 2107 return new CustomDescription.Builder(presentation); 2108 } 2109 newCustomDescription( Context context, Intent intent, String packageName)2110 public static final CustomDescription newCustomDescription( 2111 Context context, Intent intent, String packageName) { 2112 return newCustomDescriptionBuilder(context, intent, packageName).build(); 2113 } 2114 } 2115 2116 public static class FieldClassificationResult { 2117 public final AutofillId id; 2118 public final String[] categoryIds; 2119 public final float[] scores; 2120 FieldClassificationResult(@onNull AutofillId id, @NonNull String categoryId, float score)2121 public FieldClassificationResult(@NonNull AutofillId id, @NonNull String categoryId, 2122 float score) { 2123 this(id, new String[]{categoryId}, new float[]{score}); 2124 } 2125 FieldClassificationResult(@onNull AutofillId id, @NonNull String[] categoryIds, float[] scores)2126 public FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] categoryIds, 2127 float[] scores) { 2128 this.id = id; 2129 this.categoryIds = categoryIds; 2130 this.scores = scores; 2131 } 2132 } 2133 assertHasEventMatchingTypeAndFilter(int type, Consumer<Event> f, List<Event> events)2134 public static void assertHasEventMatchingTypeAndFilter(int type, 2135 Consumer<Event> f, List<Event> events) { 2136 boolean eventFound = false; 2137 for (Event event : events) { 2138 if (event.getType() == type) { 2139 f.accept(event); 2140 eventFound = true; 2141 break; 2142 } 2143 } 2144 assertThat(eventFound).isTrue(); 2145 } 2146 } 2147