1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.autofillservice.cts; 18 19 import static android.autofillservice.cts.Helper.runShellCommand; 20 import static android.provider.Settings.Secure.AUTOFILL_SERVICE; 21 22 import static com.google.common.truth.Truth.assertThat; 23 import static com.google.common.truth.Truth.assertWithMessage; 24 25 import android.app.assist.AssistStructure; 26 import android.app.assist.AssistStructure.ViewNode; 27 import android.app.assist.AssistStructure.WindowNode; 28 import android.content.Context; 29 import android.content.pm.PackageManager; 30 import android.icu.util.Calendar; 31 import android.os.UserManager; 32 import android.service.autofill.FillContext; 33 import android.support.annotation.NonNull; 34 import android.support.test.InstrumentationRegistry; 35 import android.text.TextUtils; 36 import android.util.Log; 37 import android.view.View; 38 import android.view.ViewStructure.HtmlInfo; 39 import android.view.autofill.AutofillId; 40 import android.view.autofill.AutofillValue; 41 42 import com.android.compatibility.common.util.SystemUtil; 43 44 import java.util.List; 45 import java.util.function.Function; 46 47 /** 48 * Helper for common funcionalities. 49 */ 50 final class Helper { 51 52 private static final String TAG = "AutoFillCtsHelper"; 53 54 // TODO: should static import Settings.Secure instead, but that's not a @TestApi 55 private static String USER_SETUP_COMPLETE = "user_setup_complete"; 56 57 static final boolean VERBOSE = false; 58 59 static final String ID_USERNAME_LABEL = "username_label"; 60 static final String ID_USERNAME = "username"; 61 static final String ID_PASSWORD_LABEL = "password_label"; 62 static final String ID_PASSWORD = "password"; 63 static final String ID_LOGIN = "login"; 64 static final String ID_OUTPUT = "output"; 65 66 /** Pass to {@link #setOrientation(int)} to change the display to portrait mode */ 67 public static int PORTRAIT = 0; 68 69 /** Pass to {@link #setOrientation(int)} to change the display to landscape mode */ 70 public static int LANDSCAPE = 1; 71 72 /** 73 * Timeout (in milliseconds) until framework binds / unbinds from service. 74 */ 75 static final long CONNECTION_TIMEOUT_MS = 2000; 76 77 /** 78 * Timeout (in milliseconds) until framework unbinds from a service. 79 */ 80 static final long IDLE_UNBIND_TIMEOUT_MS = 5000; 81 82 /** 83 * Timeout (in milliseconds) for expected auto-fill requests. 84 */ 85 static final long FILL_TIMEOUT_MS = 2000; 86 87 /** 88 * Timeout (in milliseconds) for expected save requests. 89 */ 90 static final long SAVE_TIMEOUT_MS = 5000; 91 92 /** 93 * Time to wait if a UI change is not expected 94 */ 95 static final long NOT_SHOWING_TIMEOUT_MS = 500; 96 97 /** 98 * Timeout (in milliseconds) for UI operations. Typically used by {@link UiBot}. 99 */ 100 static final int UI_TIMEOUT_MS = 2000; 101 102 /** 103 * Time to wait in between retries 104 */ 105 static final int RETRY_MS = 100; 106 107 private final static String ACCELLEROMETER_CHANGE = 108 "content insert --uri content://settings/system --bind name:s:accelerometer_rotation " 109 + "--bind value:i:%d"; 110 private final static String ORIENTATION_CHANGE = 111 "content insert --uri content://settings/system --bind name:s:user_rotation --bind " 112 + "value:i:%d"; 113 114 /** 115 * Runs a {@code r}, ignoring all {@link RuntimeException} and {@link Error} until the 116 * {@link #UI_TIMEOUT_MS} is reached. 117 */ eventually(Runnable r)118 static void eventually(Runnable r) throws Exception { 119 eventually(r, UI_TIMEOUT_MS); 120 } 121 122 /** 123 * Runs a {@code r}, ignoring all {@link RuntimeException} and {@link Error} until the 124 * {@code timeout} is reached. 125 */ eventually(Runnable r, int timeout)126 static void eventually(Runnable r, int timeout) throws Exception { 127 long startTime = System.currentTimeMillis(); 128 129 while (true) { 130 try { 131 r.run(); 132 break; 133 } catch (RuntimeException | Error e) { 134 if (System.currentTimeMillis() - startTime < timeout) { 135 if (VERBOSE) Log.v(TAG, "Ignoring", e); 136 Thread.sleep(RETRY_MS); 137 } else { 138 if (e instanceof RetryableException) { 139 throw e; 140 } else { 141 throw new Exception("Timedout out after " + timeout + " ms", e); 142 } 143 } 144 } 145 } 146 } 147 148 /** 149 * Runs a Shell command, returning a trimmed response. 150 */ runShellCommand(String template, Object...args)151 static String runShellCommand(String template, Object...args) { 152 final String command = String.format(template, args); 153 Log.d(TAG, "runShellCommand(): " + command); 154 try { 155 final String result = SystemUtil 156 .runShellCommand(InstrumentationRegistry.getInstrumentation(), command); 157 return TextUtils.isEmpty(result) ? "" : result.trim(); 158 } catch (Exception e) { 159 throw new RuntimeException("Command '" + command + "' failed: ", e); 160 } 161 } 162 163 /** 164 * Dump the assist structure on logcat. 165 */ dumpStructure(String message, AssistStructure structure)166 static void dumpStructure(String message, AssistStructure structure) { 167 final StringBuffer buffer = new StringBuffer(message) 168 .append(": component=") 169 .append(structure.getActivityComponent()); 170 final int nodes = structure.getWindowNodeCount(); 171 for (int i = 0; i < nodes; i++) { 172 final WindowNode windowNode = structure.getWindowNodeAt(i); 173 dump(buffer, windowNode.getRootViewNode(), " ", 0); 174 } 175 Log.i(TAG, buffer.toString()); 176 } 177 178 /** 179 * Dump the contexts on logcat. 180 */ dumpStructure(String message, List<FillContext> contexts)181 static void dumpStructure(String message, List<FillContext> contexts) { 182 for (FillContext context : contexts) { 183 dumpStructure(message, context.getStructure()); 184 } 185 } 186 187 /** 188 * Dumps the state of the autofill service on logcat. 189 */ dumpAutofillService()190 static void dumpAutofillService() { 191 Log.i(TAG, "dumpsys autofill\n\n" + runShellCommand("dumpsys autofill")); 192 } 193 194 /** 195 * Sets whether the user completed the initial setup. 196 */ setUserComplete(Context context, boolean complete)197 static void setUserComplete(Context context, boolean complete) { 198 if (isUserComplete() == complete) return; 199 200 final OneTimeSettingsListener observer = new OneTimeSettingsListener(context, 201 USER_SETUP_COMPLETE); 202 final String newValue = complete ? "1" : null; 203 runShellCommand("settings put secure %s %s default", USER_SETUP_COMPLETE, newValue); 204 observer.assertCalled(); 205 206 assertIsUserComplete(complete); 207 } 208 209 /** 210 * Gets whether the user completed the initial setup. 211 */ isUserComplete()212 static boolean isUserComplete() { 213 final String isIt = runShellCommand("settings get secure %s", USER_SETUP_COMPLETE); 214 return "1".equals(isIt); 215 } 216 217 /** 218 * Assets that user completed (or not) the initial setup. 219 */ assertIsUserComplete(boolean expected)220 static void assertIsUserComplete(boolean expected) { 221 final boolean actual = isUserComplete(); 222 assertWithMessage("Invalid value for secure setting %s", USER_SETUP_COMPLETE) 223 .that(actual).isEqualTo(expected); 224 } 225 dump(StringBuffer buffer, ViewNode node, String prefix, int childId)226 private static void dump(StringBuffer buffer, ViewNode node, String prefix, int childId) { 227 final int childrenSize = node.getChildCount(); 228 buffer.append("\n").append(prefix) 229 .append('#').append(childId).append(':') 230 .append("resId=").append(node.getIdEntry()) 231 .append(" class=").append(node.getClassName()) 232 .append(" text=").append(node.getText()) 233 .append(" class=").append(node.getClassName()) 234 .append(" #children=").append(childrenSize); 235 236 buffer.append("\n").append(prefix) 237 .append(" afId=").append(node.getAutofillId()) 238 .append(" afType=").append(node.getAutofillType()) 239 .append(" afValue=").append(node.getAutofillValue()) 240 .append(" checked=").append(node.isChecked()) 241 .append(" focused=").append(node.isFocused()); 242 243 final HtmlInfo htmlInfo = node.getHtmlInfo(); 244 if (htmlInfo != null) { 245 buffer.append("\nHtmlInfo: tag=").append(htmlInfo.getTag()) 246 .append(", attrs: ").append(htmlInfo.getAttributes()); 247 } 248 249 prefix += " "; 250 if (childrenSize > 0) { 251 for (int i = 0; i < childrenSize; i++) { 252 dump(buffer, node.getChildAt(i), prefix, i); 253 } 254 } 255 } 256 257 /** 258 * Gets a node given its Android resource id, or {@code null} if not found. 259 */ findNodeByResourceId(AssistStructure structure, String resourceId)260 static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) { 261 Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent()); 262 final int nodes = structure.getWindowNodeCount(); 263 for (int i = 0; i < nodes; i++) { 264 final WindowNode windowNode = structure.getWindowNodeAt(i); 265 final ViewNode rootNode = windowNode.getRootViewNode(); 266 final ViewNode node = findNodeByResourceId(rootNode, resourceId); 267 if (node != null) { 268 return node; 269 } 270 } 271 return null; 272 } 273 274 /** 275 * Gets a node given its Android resource id, or {@code null} if not found. 276 */ findNodeByResourceId(List<FillContext> contexts, String resourceId)277 static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) { 278 for (FillContext context : contexts) { 279 ViewNode node = findNodeByResourceId(context.getStructure(), resourceId); 280 if (node != null) { 281 return node; 282 } 283 } 284 return null; 285 } 286 287 /** 288 * Gets a node given its Android resource id, or {@code null} if not found. 289 */ findNodeByResourceId(ViewNode node, String resourceId)290 static ViewNode findNodeByResourceId(ViewNode node, String resourceId) { 291 if (resourceId.equals(node.getIdEntry())) { 292 return node; 293 } 294 final int childrenSize = node.getChildCount(); 295 if (childrenSize > 0) { 296 for (int i = 0; i < childrenSize; i++) { 297 final ViewNode found = findNodeByResourceId(node.getChildAt(i), resourceId); 298 if (found != null) { 299 return found; 300 } 301 } 302 } 303 return null; 304 } 305 306 /** 307 * Gets a node given its expected text, or {@code null} if not found. 308 */ findNodeByText(AssistStructure structure, String text)309 static ViewNode findNodeByText(AssistStructure structure, String text) { 310 Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent()); 311 final int nodes = structure.getWindowNodeCount(); 312 for (int i = 0; i < nodes; i++) { 313 final WindowNode windowNode = structure.getWindowNodeAt(i); 314 final ViewNode rootNode = windowNode.getRootViewNode(); 315 final ViewNode node = findNodeByText(rootNode, text); 316 if (node != null) { 317 return node; 318 } 319 } 320 return null; 321 } 322 323 /** 324 * Gets a node given its expected text, or {@code null} if not found. 325 */ findNodeByText(ViewNode node, String text)326 static ViewNode findNodeByText(ViewNode node, String text) { 327 if (text.equals(node.getText())) { 328 return node; 329 } 330 final int childrenSize = node.getChildCount(); 331 if (childrenSize > 0) { 332 for (int i = 0; i < childrenSize; i++) { 333 final ViewNode found = findNodeByText(node.getChildAt(i), text); 334 if (found != null) { 335 return found; 336 } 337 } 338 } 339 return null; 340 } 341 342 /** 343 * Asserts a text-base node is sanitized. 344 */ assertTextIsSanitized(ViewNode node)345 static void assertTextIsSanitized(ViewNode node) { 346 final CharSequence text = node.getText(); 347 final String resourceId = node.getIdEntry(); 348 if (!TextUtils.isEmpty(text)) { 349 throw new AssertionError("text on sanitized field " + resourceId + ": " + text); 350 } 351 assertNodeHasNoAutofillValue(node); 352 } 353 assertNodeHasNoAutofillValue(ViewNode node)354 static void assertNodeHasNoAutofillValue(ViewNode node) { 355 final AutofillValue value = node.getAutofillValue(); 356 if (value != null) { 357 final String text = value.isText() ? value.getTextValue().toString() : "N/A"; 358 throw new AssertionError("node has value: " + value + " text=" + text); 359 } 360 } 361 362 /** 363 * Asserts the contents of a text-based node that is also auto-fillable. 364 * 365 */ assertTextOnly(ViewNode node, String expectedValue)366 static void assertTextOnly(ViewNode node, String expectedValue) { 367 assertText(node, expectedValue, false); 368 } 369 370 /** 371 * Asserts the contents of a text-based node that is also auto-fillable. 372 * 373 */ assertTextAndValue(ViewNode node, String expectedValue)374 static void assertTextAndValue(ViewNode node, String expectedValue) { 375 assertText(node, expectedValue, true); 376 } 377 378 /** 379 * Asserts a text-base node exists and verify its values. 380 */ assertTextAndValue(AssistStructure structure, String resourceId, String expectedValue)381 static ViewNode assertTextAndValue(AssistStructure structure, String resourceId, 382 String expectedValue) { 383 final ViewNode node = findNodeByResourceId(structure, resourceId); 384 assertTextAndValue(node, expectedValue); 385 return node; 386 } 387 388 /** 389 * Asserts a text-base node exists and is sanitized. 390 */ assertValue(AssistStructure structure, String resourceId, String expectedValue)391 static ViewNode assertValue(AssistStructure structure, String resourceId, 392 String expectedValue) { 393 final ViewNode node = findNodeByResourceId(structure, resourceId); 394 assertTextValue(node, expectedValue); 395 return node; 396 } 397 assertText(ViewNode node, String expectedValue, boolean isAutofillable)398 private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) { 399 assertWithMessage("wrong text on %s", node).that(node.getText().toString()) 400 .isEqualTo(expectedValue); 401 final AutofillValue value = node.getAutofillValue(); 402 if (isAutofillable) { 403 assertWithMessage("null auto-fill value on %s", node).that(value).isNotNull(); 404 assertWithMessage("wrong auto-fill value on %s", node) 405 .that(value.getTextValue().toString()).isEqualTo(expectedValue); 406 } else { 407 assertWithMessage("node %s should not have AutofillValue", node).that(value).isNull(); 408 } 409 } 410 411 /** 412 * Asserts the auto-fill value of a text-based node. 413 */ assertTextValue(ViewNode node, String expectedText)414 static ViewNode assertTextValue(ViewNode node, String expectedText) { 415 final AutofillValue value = node.getAutofillValue(); 416 assertWithMessage("null autofill value on %s", node).that(value).isNotNull(); 417 assertWithMessage("wrong autofill type on %s", node).that(value.isText()).isTrue(); 418 assertWithMessage("wrong autofill value on %s", node).that(value.getTextValue().toString()) 419 .isEqualTo(expectedText); 420 return node; 421 } 422 423 /** 424 * Asserts the auto-fill value of a list-based node. 425 */ assertListValue(ViewNode node, int expectedIndex)426 static ViewNode assertListValue(ViewNode node, int expectedIndex) { 427 final AutofillValue value = node.getAutofillValue(); 428 assertWithMessage("null autofill value on %s", node).that(value).isNotNull(); 429 assertWithMessage("wrong autofill type on %s", node).that(value.isList()).isTrue(); 430 assertWithMessage("wrong autofill value on %s", node).that(value.getListValue()) 431 .isEqualTo(expectedIndex); 432 return node; 433 } 434 435 /** 436 * Asserts the auto-fill value of a toggle-based node. 437 */ assertToggleValue(ViewNode node, boolean expectedToggle)438 static void assertToggleValue(ViewNode node, boolean expectedToggle) { 439 final AutofillValue value = node.getAutofillValue(); 440 assertWithMessage("null autofill value on %s", node).that(value).isNotNull(); 441 assertWithMessage("wrong autofill type on %s", node).that(value.isToggle()).isTrue(); 442 assertWithMessage("wrong autofill value on %s", node).that(value.getToggleValue()) 443 .isEqualTo(expectedToggle); 444 } 445 446 /** 447 * Asserts the auto-fill value of a date-based node. 448 */ assertDateValue(Object object, AutofillValue value, int year, int month, int day)449 static void assertDateValue(Object object, AutofillValue value, int year, int month, int day) { 450 assertWithMessage("null autofill value on %s", object).that(value).isNotNull(); 451 assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue(); 452 453 final Calendar cal = Calendar.getInstance(); 454 cal.setTimeInMillis(value.getDateValue()); 455 456 assertWithMessage("Wrong year on AutofillValue %s", value) 457 .that(cal.get(Calendar.YEAR)).isEqualTo(year); 458 assertWithMessage("Wrong month on AutofillValue %s", value) 459 .that(cal.get(Calendar.MONTH)).isEqualTo(month); 460 assertWithMessage("Wrong day on AutofillValue %s", value) 461 .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day); 462 } 463 464 /** 465 * Asserts the auto-fill value of a date-based node. 466 */ assertDateValue(ViewNode node, int year, int month, int day)467 static void assertDateValue(ViewNode node, int year, int month, int day) { 468 assertDateValue(node, node.getAutofillValue(), year, month, day); 469 } 470 471 /** 472 * Asserts the auto-fill value of a date-based view. 473 */ assertDateValue(View view, int year, int month, int day)474 static void assertDateValue(View view, int year, int month, int day) { 475 assertDateValue(view, view.getAutofillValue(), year, month, day); 476 } 477 478 /** 479 * Asserts the auto-fill value of a time-based node. 480 */ assertTimeValue(Object object, AutofillValue value, int hour, int minute)481 private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) { 482 assertWithMessage("null autofill value on %s", object).that(value).isNotNull(); 483 assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue(); 484 485 final Calendar cal = Calendar.getInstance(); 486 cal.setTimeInMillis(value.getDateValue()); 487 488 assertWithMessage("Wrong hour on AutofillValue %s", value) 489 .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour); 490 assertWithMessage("Wrong minute on AutofillValue %s", value) 491 .that(cal.get(Calendar.MINUTE)).isEqualTo(minute); 492 } 493 494 /** 495 * Asserts the auto-fill value of a time-based node. 496 */ assertTimeValue(ViewNode node, int hour, int minute)497 static void assertTimeValue(ViewNode node, int hour, int minute) { 498 assertTimeValue(node, node.getAutofillValue(), hour, minute); 499 } 500 501 /** 502 * Asserts the auto-fill value of a time-based view. 503 */ assertTimeValue(View view, int hour, int minute)504 static void assertTimeValue(View view, int hour, int minute) { 505 assertTimeValue(view, view.getAutofillValue(), hour, minute); 506 } 507 508 /** 509 * Asserts a text-base node exists and is sanitized. 510 */ assertTextIsSanitized(AssistStructure structure, String resourceId)511 static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) { 512 final ViewNode node = findNodeByResourceId(structure, resourceId); 513 assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull(); 514 assertTextIsSanitized(node); 515 return node; 516 } 517 518 /** 519 * Asserts a list-based node exists and is sanitized. 520 */ assertListValueIsSanitized(AssistStructure structure, String resourceId)521 static void assertListValueIsSanitized(AssistStructure structure, String resourceId) { 522 final ViewNode node = findNodeByResourceId(structure, resourceId); 523 assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull(); 524 assertTextIsSanitized(node); 525 } 526 527 /** 528 * Asserts a toggle node exists and is sanitized. 529 */ assertToggleIsSanitized(AssistStructure structure, String resourceId)530 static void assertToggleIsSanitized(AssistStructure structure, String resourceId) { 531 final ViewNode node = findNodeByResourceId(structure, resourceId); 532 assertNodeHasNoAutofillValue(node); 533 assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked()) 534 .isFalse(); 535 } 536 537 /** 538 * Asserts a node exists and has the {@code expected} number of children. 539 */ assertNumberOfChildren(AssistStructure structure, String resourceId, int expected)540 static void assertNumberOfChildren(AssistStructure structure, String resourceId, int expected) { 541 final ViewNode node = findNodeByResourceId(structure, resourceId); 542 final int actual = node.getChildCount(); 543 if (actual != expected) { 544 dumpStructure("assertNumberOfChildren()", structure); 545 throw new AssertionError("assertNumberOfChildren() for " + resourceId 546 + " failed: expected " + expected + ", got " + actual); 547 } 548 } 549 550 /** 551 * Asserts the number of children in the Assist structure. 552 */ assertNumberOfChildren(AssistStructure structure, int expected)553 static void assertNumberOfChildren(AssistStructure structure, int expected) { 554 assertWithMessage("wrong number of nodes").that(structure.getWindowNodeCount()) 555 .isEqualTo(1); 556 final int actual = getNumberNodes(structure); 557 if (actual != expected) { 558 dumpStructure("assertNumberOfChildren()", structure); 559 throw new AssertionError("assertNumberOfChildren() for structure failed: expected " 560 + expected + ", got " + actual); 561 } 562 } 563 564 /** 565 * Gets the total number of nodes in an structure, including all descendants. 566 */ getNumberNodes(AssistStructure structure)567 static int getNumberNodes(AssistStructure structure) { 568 int count = 0; 569 final int nodes = structure.getWindowNodeCount(); 570 for (int i = 0; i < nodes; i++) { 571 final WindowNode windowNode = structure.getWindowNodeAt(i); 572 final ViewNode rootNode = windowNode.getRootViewNode(); 573 count += getNumberNodes(rootNode); 574 } 575 return count; 576 } 577 578 /** 579 * Gets the total number of nodes in an node, including all descendants and the node itself. 580 */ getNumberNodes(ViewNode node)581 private static int getNumberNodes(ViewNode node) { 582 int count = 1; 583 final int childrenSize = node.getChildCount(); 584 if (childrenSize > 0) { 585 for (int i = 0; i < childrenSize; i++) { 586 count += getNumberNodes(node.getChildAt(i)); 587 } 588 } 589 return count; 590 } 591 592 /** 593 * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given 594 * {@code resourceIds}. 595 */ getAutofillIds(Function<String, ViewNode> nodeResolver, String[] resourceIds)596 static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver, 597 String[] resourceIds) { 598 if (resourceIds == null) return null; 599 600 final AutofillId[] requiredIds = new AutofillId[resourceIds.length]; 601 for (int i = 0; i < resourceIds.length; i++) { 602 final String resourceId = resourceIds[i]; 603 final ViewNode node = nodeResolver.apply(resourceId); 604 if (node == null) { 605 throw new AssertionError("No node with savable resourceId " + resourceId); 606 } 607 requiredIds[i] = node.getAutofillId(); 608 609 } 610 return requiredIds; 611 } 612 613 /** 614 * Prevents the screen to rotate by itself 615 */ disableAutoRotation()616 public static void disableAutoRotation() { 617 runShellCommand(ACCELLEROMETER_CHANGE, 0); 618 setOrientation(PORTRAIT); 619 } 620 621 /** 622 * Allows the screen to rotate by itself 623 */ allowAutoRotation()624 public static void allowAutoRotation() { 625 runShellCommand(ACCELLEROMETER_CHANGE, 1); 626 } 627 628 /** 629 * Changes the screen orientation. This triggers a activity lifecycle (destroy -> create) for 630 * activities that do not handle this config change such as {@link OutOfProcessLoginActivity}. 631 * 632 * @param value {@link #PORTRAIT} or {@link #LANDSCAPE}; 633 */ setOrientation(int value)634 public static void setOrientation(int value) { 635 runShellCommand(ORIENTATION_CHANGE, value); 636 } 637 638 /** 639 * Wait until a process starts and returns the process ID of the process. 640 * 641 * @return The pid of the process 642 */ getOutOfProcessPid(@onNull String processName)643 public static int getOutOfProcessPid(@NonNull String processName) throws InterruptedException { 644 long startTime = System.currentTimeMillis(); 645 646 while (System.currentTimeMillis() - startTime < UI_TIMEOUT_MS) { 647 String[] allProcessDescs = runShellCommand("ps -eo PID,ARGS=CMD").split("\n"); 648 649 for (String processDesc : allProcessDescs) { 650 String[] pidAndName = processDesc.trim().split(" "); 651 652 if (pidAndName[1].equals(processName)) { 653 return Integer.parseInt(pidAndName[0]); 654 } 655 } 656 657 Thread.sleep(RETRY_MS); 658 } 659 660 throw new IllegalStateException("process not found"); 661 } 662 663 /** 664 * Gets the maximum number of partitions per session. 665 */ getMaxPartitions()666 public static int getMaxPartitions() { 667 return Integer.parseInt(runShellCommand("cmd autofill get max_partitions")); 668 } 669 670 /** 671 * Sets the maximum number of partitions per session. 672 */ setMaxPartitions(int value)673 public static void setMaxPartitions(int value) { 674 runShellCommand("cmd autofill set max_partitions %d", value); 675 assertThat(getMaxPartitions()).isEqualTo(value); 676 } 677 678 /** 679 * Checks if device supports the Autofill feature. 680 */ hasAutofillFeature()681 public static boolean hasAutofillFeature() { 682 return RequiredFeatureRule.hasFeature(PackageManager.FEATURE_AUTOFILL); 683 } 684 685 /** 686 * Uses Shell command to get the Autofill logging level. 687 */ getLoggingLevel()688 public static String getLoggingLevel() { 689 return runShellCommand("cmd autofill get log_level"); 690 } 691 692 /** 693 * Uses Shell command to set the Autofill logging level. 694 */ setLoggingLevel(String level)695 public static void setLoggingLevel(String level) { 696 runShellCommand("cmd autofill set log_level %s", level); 697 } 698 Helper()699 private Helper() { 700 } 701 } 702