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