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