1 /* 2 * Copyright (C) 2021 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 com.android.csuite.core; 18 19 import com.android.csuite.core.DeviceUtils.DeviceTimestamp; 20 import com.android.csuite.core.DeviceUtils.DropboxEntry; 21 import com.android.csuite.core.TestUtils.RoboscriptSignal; 22 import com.android.csuite.core.TestUtils.TestUtilsException; 23 import com.android.tradefed.device.DeviceNotAvailableException; 24 import com.android.tradefed.invoker.TestInformation; 25 import com.android.tradefed.log.LogUtil.CLog; 26 import com.android.tradefed.result.LogDataType; 27 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; 28 import com.android.tradefed.util.CommandResult; 29 import com.android.tradefed.util.CommandStatus; 30 import com.android.tradefed.util.IRunUtil; 31 import com.android.tradefed.util.RunUtil; 32 import com.android.tradefed.util.ZipUtil; 33 34 import com.google.common.annotations.VisibleForTesting; 35 import com.google.common.base.Preconditions; 36 import com.google.common.io.MoreFiles; 37 38 import org.junit.Assert; 39 40 import java.io.File; 41 import java.io.IOException; 42 import java.nio.charset.Charset; 43 import java.nio.file.FileSystem; 44 import java.nio.file.FileSystems; 45 import java.nio.file.Files; 46 import java.nio.file.Path; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.List; 50 import java.util.Optional; 51 import java.util.concurrent.atomic.AtomicReference; 52 import java.util.regex.Matcher; 53 import java.util.regex.Pattern; 54 import java.util.stream.Collectors; 55 import java.util.stream.Stream; 56 57 import javax.annotation.Nullable; 58 59 /** A tester that interact with an app crawler during testing. */ 60 public final class AppCrawlTester { 61 @VisibleForTesting Path mOutput; 62 private final RunUtilProvider mRunUtilProvider; 63 private final TestUtils mTestUtils; 64 private final String mPackageName; 65 private boolean mRecordScreen = false; 66 private boolean mCollectGmsVersion = false; 67 private boolean mCollectAppVersion = false; 68 private boolean mUiAutomatorMode = false; 69 private int mTimeoutSec; 70 private String mCrawlControllerEndpoint; 71 private Path mApkRoot; 72 private Path mRoboscriptFile; 73 private Path mCrawlGuidanceProtoFile; 74 private Path mLoginConfigDir; 75 private FileSystem mFileSystem; 76 private DeviceTimestamp mScreenRecordStartTime; 77 78 /** 79 * Creates an {@link AppCrawlTester} instance. 80 * 81 * @param packageName The package name of the apk files. 82 * @param testInformation The TradeFed test information. 83 * @param testLogData The TradeFed test output receiver. 84 * @return an {@link AppCrawlTester} instance. 85 */ newInstance( String packageName, TestInformation testInformation, TestLogData testLogData)86 public static AppCrawlTester newInstance( 87 String packageName, TestInformation testInformation, TestLogData testLogData) { 88 return new AppCrawlTester( 89 packageName, 90 TestUtils.getInstance(testInformation, testLogData), 91 () -> new RunUtil(), 92 FileSystems.getDefault()); 93 } 94 95 @VisibleForTesting AppCrawlTester( String packageName, TestUtils testUtils, RunUtilProvider runUtilProvider, FileSystem fileSystem)96 AppCrawlTester( 97 String packageName, 98 TestUtils testUtils, 99 RunUtilProvider runUtilProvider, 100 FileSystem fileSystem) { 101 mRunUtilProvider = runUtilProvider; 102 mPackageName = packageName; 103 mTestUtils = testUtils; 104 mFileSystem = fileSystem; 105 } 106 107 /** An exception class representing crawler test failures. */ 108 public static final class CrawlerException extends Exception { 109 /** 110 * Constructs a new {@link CrawlerException} with a meaningful error message. 111 * 112 * @param message A error message describing the cause of the error. 113 */ CrawlerException(String message)114 private CrawlerException(String message) { 115 super(message); 116 } 117 118 /** 119 * Constructs a new {@link CrawlerException} with a meaningful error message, and a cause. 120 * 121 * @param message A detailed error message. 122 * @param cause A {@link Throwable} capturing the original cause of the CrawlerException. 123 */ CrawlerException(String message, Throwable cause)124 private CrawlerException(String message, Throwable cause) { 125 super(message, cause); 126 } 127 128 /** 129 * Constructs a new {@link CrawlerException} with a cause. 130 * 131 * @param cause A {@link Throwable} capturing the original cause of the CrawlerException. 132 */ CrawlerException(Throwable cause)133 private CrawlerException(Throwable cause) { 134 super(cause); 135 } 136 } 137 138 /** 139 * Starts crawling the app and throw AssertionError if app crash is detected. 140 * 141 * @throws DeviceNotAvailableException When device because unavailable. 142 */ startAndAssertAppNoCrash()143 public void startAndAssertAppNoCrash() throws DeviceNotAvailableException { 144 DeviceTimestamp startTime = mTestUtils.getDeviceUtils().currentTimeMillis(); 145 146 CrawlerException crawlerException = null; 147 try { 148 start(); 149 } catch (CrawlerException e) { 150 crawlerException = e; 151 } 152 DeviceTimestamp endTime = mTestUtils.getDeviceUtils().currentTimeMillis(); 153 154 ArrayList<String> failureMessages = new ArrayList<>(); 155 156 try { 157 158 List<DropboxEntry> crashEntries = 159 mTestUtils 160 .getDeviceUtils() 161 .getDropboxEntries( 162 DeviceUtils.DROPBOX_APP_CRASH_TAGS, 163 mPackageName, 164 startTime, 165 endTime); 166 String dropboxCrashLog = 167 mTestUtils.compileTestFailureMessage( 168 mPackageName, crashEntries, true, mScreenRecordStartTime); 169 170 if (dropboxCrashLog != null) { 171 // Put dropbox crash log on the top of the failure messages. 172 failureMessages.add(dropboxCrashLog); 173 } 174 } catch (IOException e) { 175 failureMessages.add("Error while getting dropbox crash log: " + e.getMessage()); 176 } 177 178 if (crawlerException != null) { 179 failureMessages.add(crawlerException.getMessage()); 180 } 181 182 if (!failureMessages.isEmpty()) { 183 Assert.fail( 184 String.join( 185 "\n============\n", 186 failureMessages.toArray(new String[failureMessages.size()]))); 187 } 188 } 189 190 /** 191 * Starts a crawler run on the configured app. 192 * 193 * @throws CrawlerException When the crawler was not set up correctly or the crawler run command 194 * failed. 195 * @throws DeviceNotAvailableException When device because unavailable. 196 */ start()197 public void start() throws CrawlerException, DeviceNotAvailableException { 198 if (!AppCrawlTesterHostPreparer.isReady(mTestUtils.getTestInformation())) { 199 throw new CrawlerException( 200 "The " 201 + AppCrawlTesterHostPreparer.class.getName() 202 + " is not ready. Please check whether " 203 + AppCrawlTesterHostPreparer.class.getName() 204 + " was included in the test plan and completed successfully."); 205 } 206 207 if (mOutput != null) { 208 throw new CrawlerException( 209 "The crawler has already run. Multiple runs in the same " 210 + AppCrawlTester.class.getName() 211 + " instance are not supported."); 212 } 213 214 try { 215 mOutput = Files.createTempDirectory("crawler"); 216 } catch (IOException e) { 217 throw new CrawlerException("Failed to create temp directory for output.", e); 218 } 219 220 IRunUtil runUtil = mRunUtilProvider.get(); 221 AtomicReference<String[]> command = new AtomicReference<>(); 222 AtomicReference<CommandResult> commandResult = new AtomicReference<>(); 223 224 CLog.d("Start to crawl package: %s.", mPackageName); 225 226 Path bin = 227 mFileSystem.getPath( 228 AppCrawlTesterHostPreparer.getCrawlerBinPath( 229 mTestUtils.getTestInformation())); 230 boolean isUtpClient = false; 231 if (Files.exists(bin.resolve("utp-cli-android_deploy.jar"))) { 232 command.set(createUtpCrawlerRunCommand(mTestUtils.getTestInformation())); 233 runUtil.setEnvVariable( 234 "ANDROID_SDK", 235 AppCrawlTesterHostPreparer.getSdkPath(mTestUtils.getTestInformation()) 236 .toString()); 237 isUtpClient = true; 238 } else if (Files.exists(bin.resolve("crawl_launcher_deploy.jar"))) { 239 command.set(createCrawlerRunCommand(mTestUtils.getTestInformation())); 240 runUtil.setEnvVariable( 241 "GOOGLE_APPLICATION_CREDENTIALS", 242 AppCrawlTesterHostPreparer.getCredentialPath(mTestUtils.getTestInformation()) 243 .toString()); 244 } else { 245 throw new CrawlerException( 246 "Crawler executable binaries not found in " + bin.toString()); 247 } 248 249 if (mCollectGmsVersion) { 250 mTestUtils.collectGmsVersion(mPackageName); 251 } 252 253 // Minimum timeout 3 minutes plus crawl test timeout. 254 long commandTimeout = 3 * 60 * 1000 + mTimeoutSec * 1000; 255 256 // TODO(yuexima): When the obb_file option is supported in espresso mode, the timeout need 257 // to be extended. 258 if (mRecordScreen) { 259 mTestUtils.collectScreenRecord( 260 () -> { 261 commandResult.set(runUtil.runTimedCmd(commandTimeout, command.get())); 262 }, 263 mPackageName, 264 deviceTime -> mScreenRecordStartTime = deviceTime); 265 } else { 266 commandResult.set(runUtil.runTimedCmd(commandTimeout, command.get())); 267 } 268 269 // Must be done after the crawler run because the app is installed by the crawler. 270 if (mCollectAppVersion) { 271 mTestUtils.collectAppVersion(mPackageName); 272 } 273 274 collectOutputZip(); 275 collectCrawlStepScreenshots(isUtpClient); 276 createCrawlerRoboscriptSignal(isUtpClient); 277 278 if (!commandResult.get().getStatus().equals(CommandStatus.SUCCESS) 279 || commandResult.get().getStdout().contains("Unknown options:")) { 280 throw new CrawlerException("Crawler command failed: " + commandResult.get()); 281 } 282 283 CLog.i("Completed crawling the package %s. Outputs: %s", mPackageName, commandResult.get()); 284 } 285 286 /** Copys the step screenshots into test outputs for easier access. */ collectCrawlStepScreenshots(boolean isUtpClient)287 private void collectCrawlStepScreenshots(boolean isUtpClient) { 288 if (mOutput == null) { 289 CLog.e("Output directory is not created yet. Skipping collecting step screenshots."); 290 return; 291 } 292 293 Path subDir = getClientCrawlerOutputSubDir(isUtpClient); 294 if (!Files.exists(subDir)) { 295 CLog.e( 296 "The crawler output directory is not complete, skipping collecting step" 297 + " screenshots."); 298 return; 299 } 300 301 try (Stream<Path> files = Files.list(subDir)) { 302 files.filter(path -> path.getFileName().toString().toLowerCase().endsWith(".png")) 303 .forEach( 304 path -> { 305 mTestUtils 306 .getTestArtifactReceiver() 307 .addTestArtifact( 308 mPackageName 309 + "-crawl_step_screenshot_" 310 + path.getFileName(), 311 LogDataType.PNG, 312 path.toFile()); 313 }); 314 } catch (IOException e) { 315 CLog.e(e); 316 } 317 } 318 319 /** 320 * Reads the crawler output and creates an artifact with the success signal for a Roboscript 321 * that has been executed by the crawler. 322 */ createCrawlerRoboscriptSignal(boolean isUtpClient)323 private void createCrawlerRoboscriptSignal(boolean isUtpClient) { 324 if (mOutput == null) { 325 CLog.e("Output directory is not created yet. Skipping collecting crawler signal."); 326 return; 327 } 328 329 Path subDir = getClientCrawlerOutputSubDir(isUtpClient); 330 if (!Files.exists(subDir)) { 331 CLog.e( 332 "The crawler output directory is not complete, skipping collecting crawler" 333 + " signal."); 334 return; 335 } 336 337 try (Stream<Path> files = Files.list(subDir)) { 338 Optional<Path> roboOutputFile = 339 files.filter( 340 path -> 341 path.getFileName() 342 .toString() 343 .toLowerCase() 344 .endsWith("crawl_outputs.txt")) 345 .findFirst(); 346 if (roboOutputFile.isPresent()) { 347 generateRoboscriptSignalFile(roboOutputFile.get(), mPackageName); 348 } 349 } catch (IOException e) { 350 CLog.e(e); 351 } 352 } 353 354 /** 355 * Generates an artifact text file with a name indicating whether the Roboscript was successful. 356 * 357 * @param roboOutputFile - the file containing the Robo crawler output. 358 * @param packageName - the android package name of the app for which the signal file is being 359 * generated. 360 */ generateRoboscriptSignalFile(Path roboOutputFile, String packageName)361 private void generateRoboscriptSignalFile(Path roboOutputFile, String packageName) { 362 try { 363 File signalFile = 364 Files.createTempFile( 365 packageName 366 + "_roboscript_" 367 + getRoboscriptSignal(Optional.of(roboOutputFile)) 368 .toString() 369 .toLowerCase(), 370 ".txt") 371 .toFile(); 372 mTestUtils 373 .getTestArtifactReceiver() 374 .addTestArtifact(signalFile.getName(), LogDataType.HOST_LOG, signalFile); 375 } catch (IOException e) { 376 CLog.e(e); 377 } 378 } 379 380 /** 381 * Computes whether the Robosript was successful based on the output file, and returns the 382 * success signal. 383 * 384 * @param roboOutput 385 * @return Roboscript success signal 386 */ getRoboscriptSignal(Optional<Path> roboOutput)387 public RoboscriptSignal getRoboscriptSignal(Optional<Path> roboOutput) { 388 if (!roboOutput.isPresent()) { 389 return RoboscriptSignal.UNKNOWN; 390 } 391 Pattern totalActionsPattern = 392 Pattern.compile("(?:robo_script_execution(?:.|\\n)*)total_actions.\\s(\\d*)"); 393 Pattern successfulActionsPattern = 394 Pattern.compile("(?:robo_script_execution(?:.|\\n)*)successful_actions.\\s(\\d*)"); 395 final String outputFile; 396 try { 397 outputFile = 398 String.join("", Files.readAllLines(roboOutput.get(), Charset.defaultCharset())); 399 } catch (IOException e) { 400 CLog.e(e); 401 return RoboscriptSignal.UNKNOWN; 402 } 403 int totalActions = 0; 404 int successfulActions = 0; 405 Matcher mTotal = totalActionsPattern.matcher(outputFile); 406 Matcher mSuccessful = successfulActionsPattern.matcher(outputFile); 407 if (mTotal.find() && mSuccessful.find()) { 408 totalActions = Integer.parseInt(mTotal.group(1)); 409 successfulActions = Integer.parseInt(mSuccessful.group(1)); 410 if (totalActions == 0) { 411 return RoboscriptSignal.FAIL; 412 } 413 return successfulActions / totalActions < 1 414 ? RoboscriptSignal.FAIL 415 : RoboscriptSignal.SUCCESS; 416 } 417 return RoboscriptSignal.UNKNOWN; 418 } 419 420 /** Based on the type of Robo client, resolves the Path for its output directory. */ getClientCrawlerOutputSubDir(boolean isUtpClient)421 private Path getClientCrawlerOutputSubDir(boolean isUtpClient) { 422 return isUtpClient 423 ? mOutput.resolve("output").resolve("artifacts") 424 : mOutput.resolve("app_firebase_test_lab"); 425 } 426 427 /** Puts the zipped crawler output files into test output. */ collectOutputZip()428 private void collectOutputZip() { 429 if (mOutput == null) { 430 CLog.e("Output directory is not created yet. Skipping collecting output."); 431 return; 432 } 433 434 // Compress the crawler output directory and add it to test outputs. 435 try { 436 File outputZip = ZipUtil.createZip(mOutput.toFile()); 437 mTestUtils 438 .getTestArtifactReceiver() 439 .addTestArtifact(mPackageName + "-crawler_output", LogDataType.ZIP, outputZip); 440 } catch (IOException e) { 441 CLog.e("Failed to zip the output directory: " + e); 442 } 443 } 444 445 @VisibleForTesting createUtpCrawlerRunCommand(TestInformation testInfo)446 String[] createUtpCrawlerRunCommand(TestInformation testInfo) throws CrawlerException { 447 448 Path bin = 449 mFileSystem.getPath( 450 AppCrawlTesterHostPreparer.getCrawlerBinPath( 451 mTestUtils.getTestInformation())); 452 ArrayList<String> cmd = new ArrayList<>(); 453 cmd.addAll( 454 Arrays.asList( 455 "java", 456 "-jar", 457 bin.resolve("utp-cli-android_deploy.jar").toString(), 458 "android", 459 "robo", 460 "--device-id", 461 testInfo.getDevice().getSerialNumber(), 462 "--app-id", 463 mPackageName, 464 "--controller-endpoint", 465 "PROD", 466 "--utp-binaries-dir", 467 bin.toString(), 468 "--key-file", 469 AppCrawlTesterHostPreparer.getCredentialPath( 470 mTestUtils.getTestInformation()) 471 .toString(), 472 "--base-crawler-apk", 473 bin.resolve("crawler_app.apk").toString(), 474 "--stub-crawler-apk", 475 bin.resolve("crawler_stubapp_androidx.apk").toString(), 476 "--tmp-dir", 477 mOutput.toString())); 478 479 if (mTimeoutSec > 0) { 480 cmd.add("--crawler-flag"); 481 cmd.add("crawlDurationSec=" + Integer.toString(mTimeoutSec)); 482 } 483 484 if (mUiAutomatorMode) { 485 cmd.addAll(Arrays.asList("--ui-automator-mode", "--app-installed-on-device")); 486 } else { 487 Preconditions.checkNotNull( 488 mApkRoot, "Apk file path is required when not running in UIAutomator mode"); 489 490 try { 491 TestUtils.listApks(mApkRoot) 492 .forEach( 493 path -> { 494 String nameLowercase = 495 path.getFileName().toString().toLowerCase(); 496 if (nameLowercase.endsWith(".apk")) { 497 cmd.add("--apks-to-crawl"); 498 cmd.add(path.toString()); 499 } else if (nameLowercase.endsWith(".obb")) { 500 cmd.add("--files-to-push"); 501 cmd.add( 502 String.format( 503 "%s=/sdcard/Android/obb/%s/%s", 504 path.toString(), 505 mPackageName, 506 path.getFileName().toString())); 507 } else { 508 CLog.d("Skipping unrecognized file %s", path.toString()); 509 } 510 }); 511 } catch (TestUtilsException e) { 512 throw new CrawlerException(e); 513 } 514 } 515 516 if (mRoboscriptFile != null) { 517 Assert.assertTrue( 518 "Please provide a valid roboscript file.", 519 Files.isRegularFile(mRoboscriptFile)); 520 cmd.add("--crawler-asset"); 521 cmd.add("robo.script=" + mRoboscriptFile.toString()); 522 } 523 524 if (mCrawlGuidanceProtoFile != null) { 525 Assert.assertTrue( 526 "Please provide a valid CrawlGuidance file.", 527 Files.isRegularFile(mCrawlGuidanceProtoFile)); 528 cmd.add("--crawl-guidance-proto-path"); 529 cmd.add(mCrawlGuidanceProtoFile.toString()); 530 } 531 532 if (mLoginConfigDir != null) { 533 RoboLoginConfigProvider configProvider = new RoboLoginConfigProvider(mLoginConfigDir); 534 cmd.addAll(configProvider.findConfigFor(mPackageName, true).getLoginArgs()); 535 } 536 537 return cmd.toArray(new String[cmd.size()]); 538 } 539 540 @VisibleForTesting createCrawlerRunCommand(TestInformation testInfo)541 String[] createCrawlerRunCommand(TestInformation testInfo) throws CrawlerException { 542 543 Path bin = 544 mFileSystem.getPath( 545 AppCrawlTesterHostPreparer.getCrawlerBinPath( 546 mTestUtils.getTestInformation())); 547 ArrayList<String> cmd = new ArrayList<>(); 548 cmd.addAll( 549 Arrays.asList( 550 "java", 551 "-jar", 552 bin.resolve("crawl_launcher_deploy.jar").toString(), 553 "--android-sdk-path", 554 AppCrawlTesterHostPreparer.getSdkPath(testInfo).toString(), 555 "--device-serial-code", 556 testInfo.getDevice().getSerialNumber(), 557 "--output-dir", 558 mOutput.toString(), 559 "--key-store-file", 560 // Using the publicly known default file name of the debug keystore. 561 bin.resolve("debug.keystore").toString(), 562 "--key-store-password", 563 // Using the publicly known default password of the debug keystore. 564 "android")); 565 566 if (mCrawlControllerEndpoint != null && mCrawlControllerEndpoint.length() > 0) { 567 cmd.addAll(Arrays.asList("--endpoint", mCrawlControllerEndpoint)); 568 } 569 570 if (mUiAutomatorMode) { 571 cmd.addAll(Arrays.asList("--ui-automator-mode", "--app-package-name", mPackageName)); 572 } else { 573 Preconditions.checkNotNull( 574 mApkRoot, "Apk file path is required when not running in UIAutomator mode"); 575 576 List<Path> apks; 577 try { 578 apks = 579 TestUtils.listApks(mApkRoot).stream() 580 .filter( 581 path -> 582 path.getFileName() 583 .toString() 584 .toLowerCase() 585 .endsWith(".apk")) 586 .collect(Collectors.toList()); 587 } catch (TestUtilsException e) { 588 throw new CrawlerException(e); 589 } 590 591 cmd.add("--apk-file"); 592 cmd.add(apks.get(0).toString()); 593 594 for (int i = 1; i < apks.size(); i++) { 595 cmd.add("--split-apk-files"); 596 cmd.add(apks.get(i).toString()); 597 } 598 } 599 600 if (mTimeoutSec > 0) { 601 cmd.add("--timeout-sec"); 602 cmd.add(Integer.toString(mTimeoutSec)); 603 } 604 605 if (mRoboscriptFile != null) { 606 Assert.assertTrue( 607 "Please provide a valid roboscript file.", 608 Files.isRegularFile(mRoboscriptFile)); 609 cmd.addAll(Arrays.asList("--robo-script-file", mRoboscriptFile.toString())); 610 } 611 612 if (mCrawlGuidanceProtoFile != null) { 613 Assert.assertTrue( 614 "Please provide a valid CrawlGuidance file.", 615 Files.isRegularFile(mCrawlGuidanceProtoFile)); 616 cmd.addAll(Arrays.asList("--text-guide-file", mCrawlGuidanceProtoFile.toString())); 617 } 618 619 if (mLoginConfigDir != null) { 620 RoboLoginConfigProvider configProvider = new RoboLoginConfigProvider(mLoginConfigDir); 621 cmd.addAll(configProvider.findConfigFor(mPackageName, false).getLoginArgs()); 622 } 623 624 return cmd.toArray(new String[cmd.size()]); 625 } 626 627 /** Cleans up the crawler output directory. */ cleanUp()628 public void cleanUp() { 629 if (mOutput == null) { 630 return; 631 } 632 633 try { 634 MoreFiles.deleteRecursively(mOutput); 635 } catch (IOException e) { 636 CLog.e("Failed to clean up the crawler output directory: " + e); 637 } 638 } 639 640 /** Sets the option of whether to record the device screen during crawling. */ setRecordScreen(boolean recordScreen)641 public void setRecordScreen(boolean recordScreen) { 642 mRecordScreen = recordScreen; 643 } 644 645 /** Sets the option of whether to collect GMS version in test artifacts. */ setCollectGmsVersion(boolean collectGmsVersion)646 public void setCollectGmsVersion(boolean collectGmsVersion) { 647 mCollectGmsVersion = collectGmsVersion; 648 } 649 650 /** Sets the option of whether to collect the app version in test artifacts. */ setCollectAppVersion(boolean collectAppVersion)651 public void setCollectAppVersion(boolean collectAppVersion) { 652 mCollectAppVersion = collectAppVersion; 653 } 654 655 /** Sets the option of whether to run the crawler with UIAutomator mode. */ setUiAutomatorMode(boolean uiAutomatorMode)656 public void setUiAutomatorMode(boolean uiAutomatorMode) { 657 mUiAutomatorMode = uiAutomatorMode; 658 } 659 660 /** Sets the value of the "timeout-sec" param for the crawler launcher. */ setTimeoutSec(int timeoutSec)661 public void setTimeoutSec(int timeoutSec) { 662 mTimeoutSec = timeoutSec; 663 } 664 665 /** Sets the robo crawler controller endpoint (optional). */ setCrawlControllerEndpoint(String crawlControllerEndpoint)666 public void setCrawlControllerEndpoint(String crawlControllerEndpoint) { 667 mCrawlControllerEndpoint = crawlControllerEndpoint; 668 } 669 670 /** 671 * Sets the apk file path. Required when not running in UIAutomator mode. 672 * 673 * @param apkRoot The root path for an apk or a directory that contains apk files for a package. 674 */ setApkPath(Path apkRoot)675 public void setApkPath(Path apkRoot) { 676 mApkRoot = apkRoot; 677 } 678 679 /** 680 * Sets the option of the Roboscript file to be used by the crawler. Null can be passed to 681 * remove the reference to the file. 682 */ setRoboscriptFile(@ullable Path roboscriptFile)683 public void setRoboscriptFile(@Nullable Path roboscriptFile) { 684 mRoboscriptFile = roboscriptFile; 685 } 686 687 /** 688 * Sets the option of the CrawlGuidance file to be used by the crawler. Null can be passed to 689 * remove the reference to the file. 690 */ setCrawlGuidanceProtoFile(@ullable Path crawlGuidanceProtoFile)691 public void setCrawlGuidanceProtoFile(@Nullable Path crawlGuidanceProtoFile) { 692 mCrawlGuidanceProtoFile = crawlGuidanceProtoFile; 693 } 694 695 /** Sets the option of the directory that contains configuration for login. */ setLoginConfigDir(@ullable Path loginFilesDir)696 public void setLoginConfigDir(@Nullable Path loginFilesDir) { 697 mLoginConfigDir = loginFilesDir; 698 } 699 700 @VisibleForTesting 701 interface RunUtilProvider { get()702 IRunUtil get(); 703 } 704 } 705