1 /* 2 * Copyright (C) 2019 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.tradefed.device; 18 19 import com.android.ddmlib.IDevice; 20 import com.android.ddmlib.Log.LogLevel; 21 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; 22 import com.android.tradefed.build.IBuildInfo; 23 import com.android.tradefed.device.cloud.GceAvdInfo; 24 import com.android.tradefed.device.connection.AdbTcpConnection; 25 import com.android.tradefed.device.connection.DefaultConnection; 26 import com.android.tradefed.device.connection.DefaultConnection.ConnectionBuilder; 27 import com.android.tradefed.log.ITestLogger; 28 import com.android.tradefed.log.LogUtil.CLog; 29 import com.android.tradefed.result.FileInputStreamSource; 30 import com.android.tradefed.result.ITestLoggerReceiver; 31 import com.android.tradefed.result.InputStreamSource; 32 import com.android.tradefed.result.error.DeviceErrorIdentifier; 33 import com.android.tradefed.result.error.InfraErrorIdentifier; 34 import com.android.tradefed.targetprep.TargetSetupError; 35 import com.android.tradefed.util.CommandResult; 36 import com.android.tradefed.util.CommandStatus; 37 import com.android.tradefed.util.FileUtil; 38 import com.android.tradefed.util.IRunUtil; 39 import com.android.tradefed.util.MultiMap; 40 import com.android.tradefed.util.TarUtil; 41 import com.android.tradefed.util.ZipUtil; 42 43 import com.google.common.annotations.VisibleForTesting; 44 import com.google.common.base.Strings; 45 import com.google.common.net.HostAndPort; 46 47 import java.io.File; 48 import java.io.IOException; 49 import java.nio.file.Path; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.List; 53 import java.util.Map; 54 55 /** The class for local virtual devices running on TradeFed host. */ 56 public class LocalAndroidVirtualDevice extends RemoteAndroidDevice implements ITestLoggerReceiver { 57 58 private static final int INVALID_PORT = 0; 59 60 // Environment variables. 61 private static final String ANDROID_SOONG_HOST_OUT = "ANDROID_SOONG_HOST_OUT"; 62 private static final String TMPDIR = "TMPDIR"; 63 64 // The build info key of the cuttlefish tools. 65 private static final String CVD_HOST_PACKAGE_NAME = "cvd-host_package.tar.gz"; 66 // The optional build info keys for mixing images. 67 private static final String BOOT_IMAGE_ZIP_NAME = "boot-img.zip"; 68 private static final String SYSTEM_IMAGE_ZIP_NAME = "system-img.zip"; 69 private static final String OTA_TOOLS_ZIP_NAME = "otatools.zip"; 70 71 // Acloud option names. 72 private static final String ACLOUD_LOCAL_TOOL_OPTION = "local-tool"; 73 private static final String ACLOUD_LOCAL_IMAGE_OPTION = "local-image"; 74 75 private ITestLogger mTestLogger = null; 76 77 // Temporary directories for images, runtime files, and tools. 78 private File mImageDir = null; 79 private File mInstanceDir = null; 80 private File mHostPackageDir = null; 81 private File mBootImageDir = null; 82 private File mSystemImageDir = null; 83 private File mOtaToolsDir = null; 84 private List<File> mTempDirs = new ArrayList<File>(); 85 86 private GceAvdInfo mGceAvdInfo = null; 87 private boolean mCanShutdown = false; 88 LocalAndroidVirtualDevice( IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor)89 public LocalAndroidVirtualDevice( 90 IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor) { 91 super(device, stateMonitor, allocationMonitor); 92 } 93 94 /** Execute common setup procedure and launch the virtual device. */ 95 @Override preInvocationSetup( IBuildInfo info, MultiMap<String, String> attributes)96 public synchronized void preInvocationSetup( 97 IBuildInfo info, MultiMap<String, String> attributes) 98 throws TargetSetupError, DeviceNotAvailableException { 99 resetAttributes(); 100 // The setup method in super class does not require the device to be online. 101 super.preInvocationSetup(info, attributes); 102 103 prepareToolsAndImages(info); 104 105 CommandResult result = null; 106 File report = null; 107 try { 108 report = FileUtil.createTempFile("report", ".json"); 109 result = acloudCreate(report, getOptions()); 110 loadAvdInfo(report); 111 } catch (IOException ex) { 112 throw new TargetSetupError( 113 "Cannot create acloud report file.", 114 ex, 115 getDeviceDescriptor(), 116 InfraErrorIdentifier.FAIL_TO_CREATE_FILE); 117 } finally { 118 FileUtil.deleteFile(report); 119 } 120 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 121 throw new TargetSetupError( 122 String.format("Cannot execute acloud command. stderr:\n%s", result.getStderr()), 123 getDeviceDescriptor(), 124 InfraErrorIdentifier.ACLOUD_UNDETERMINED); 125 } 126 127 HostAndPort hostAndPort = mGceAvdInfo.hostAndPort(); 128 replaceStubDevice(hostAndPort.toString()); 129 130 RecoveryMode previousMode = getRecoveryMode(); 131 try { 132 setRecoveryMode(RecoveryMode.NONE); 133 if (!adbTcpConnect(hostAndPort.getHost(), Integer.toString(hostAndPort.getPort()))) { 134 throw new TargetSetupError( 135 String.format("Cannot connect to %s.", hostAndPort), 136 getDeviceDescriptor(), 137 DeviceErrorIdentifier.FAILED_TO_CONNECT_TO_GCE); 138 } 139 waitForDeviceAvailable(); 140 } finally { 141 setRecoveryMode(previousMode); 142 } 143 } 144 145 /** Execute common tear-down procedure and stop the virtual device. */ 146 @Override postInvocationTearDown(Throwable exception)147 public synchronized void postInvocationTearDown(Throwable exception) { 148 TestDeviceOptions options = getOptions(); 149 HostAndPort hostAndPort = getHostAndPortFromAvdInfo(); 150 String instanceName = (mGceAvdInfo != null ? mGceAvdInfo.instanceName() : null); 151 try { 152 shutdown(); 153 reportInstanceLogs(); 154 } finally { 155 restoreStubDevice(); 156 157 if (!options.shouldSkipTearDown()) { 158 deleteTempDirs(); 159 } else { 160 CLog.i( 161 "Skip deleting the temporary directories.\n" 162 + "Address: %s\nName: %s\n" 163 + "Host package: %s\nImage: %s\nInstance: %s\n" 164 + "Boot image: %s\nSystem image: %s\nOTA tools: %s", 165 hostAndPort, 166 instanceName, 167 mHostPackageDir, 168 mImageDir, 169 mInstanceDir, 170 mBootImageDir, 171 mSystemImageDir, 172 mOtaToolsDir); 173 } 174 resetAttributes(); 175 176 super.postInvocationTearDown(exception); 177 } 178 } 179 180 @Override setTestLogger(ITestLogger testLogger)181 public void setTestLogger(ITestLogger testLogger) { 182 mTestLogger = testLogger; 183 super.setTestLogger(testLogger); 184 } 185 186 /** 187 * Extract a file if the format is tar.gz or zip. 188 * 189 * @param file the file to be extracted. 190 * @return a temporary directory containing the extracted content if the file is an archive; 191 * otherwise return the input file. 192 * @throws IOException if the file cannot be extracted. 193 */ extractArchive(File file)194 private File extractArchive(File file) throws IOException { 195 if (file.isDirectory()) { 196 return file; 197 } 198 if (TarUtil.isGzip(file)) { 199 file = TarUtil.extractTarGzipToTemp(file, file.getName()); 200 mTempDirs.add(file); 201 } else if (ZipUtil.isZipFileValid(file, false)) { 202 file = ZipUtil.extractZipToTemp(file, file.getName()); 203 mTempDirs.add(file); 204 } else { 205 CLog.w("Cannot extract %s.", file); 206 } 207 return file; 208 } 209 210 /** Find a file in build info and extract it to a temporary directory. */ findAndExtractFile(IBuildInfo buildInfo, String fileKey)211 private File findAndExtractFile(IBuildInfo buildInfo, String fileKey) throws TargetSetupError { 212 File file = buildInfo.getFile(fileKey); 213 try { 214 return file != null ? extractArchive(file) : null; 215 } catch (IOException ex) { 216 throw new TargetSetupError( 217 String.format("Cannot extract %s.", fileKey), 218 ex, 219 getDeviceDescriptor(), 220 InfraErrorIdentifier.FAIL_TO_CREATE_FILE); 221 } 222 } 223 224 /** Find a file in build info and extract it; fall back to environment variable. */ findAndExtractFile(IBuildInfo buildInfo, String fileKey, String envVar)225 private File findAndExtractFile(IBuildInfo buildInfo, String fileKey, String envVar) 226 throws TargetSetupError { 227 File dir = findAndExtractFile(buildInfo, fileKey); 228 if (dir != null) { 229 return dir; 230 } 231 232 String envDir = System.getenv(envVar); 233 if (!Strings.isNullOrEmpty(envDir)) { 234 dir = new File(envDir); 235 if (dir.isDirectory()) { 236 CLog.i( 237 "Use the files in %s as the build info does not provide %s.", 238 envVar, fileKey); 239 return dir; 240 } 241 CLog.w("Cannot use the files in %s as it is not a directory.", envVar); 242 } 243 return null; 244 } 245 246 /** Create a temporary directory that will be deleted when teardown. */ createTempDir()247 private File createTempDir() throws TargetSetupError { 248 try { 249 File tempDir = FileUtil.createTempDir("LocalVirtualDevice"); 250 mTempDirs.add(tempDir); 251 return tempDir; 252 } catch (IOException ex) { 253 throw new TargetSetupError( 254 "Cannot create temporary directory.", 255 ex, 256 getDeviceDescriptor(), 257 InfraErrorIdentifier.FAIL_TO_CREATE_FILE); 258 } 259 } 260 261 /** Get the necessary files to create the instance. */ prepareToolsAndImages(IBuildInfo info)262 private void prepareToolsAndImages(IBuildInfo info) throws TargetSetupError { 263 MultiMap<String, File> fileMap = getOptions().getGceDriverFileParams(); 264 try { 265 mHostPackageDir = 266 findAndExtractFile(info, CVD_HOST_PACKAGE_NAME, ANDROID_SOONG_HOST_OUT); 267 if (mHostPackageDir == null && !fileMap.containsKey(ACLOUD_LOCAL_TOOL_OPTION)) { 268 throw new TargetSetupError( 269 String.format( 270 "Cannot find %s in build info and %s.", 271 CVD_HOST_PACKAGE_NAME, ANDROID_SOONG_HOST_OUT), 272 getDeviceDescriptor(), 273 InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND); 274 } 275 mImageDir = findAndExtractFile(info, BuildInfoFileKey.DEVICE_IMAGE.getFileKey()); 276 if (mImageDir == null && !fileMap.containsKey(ACLOUD_LOCAL_IMAGE_OPTION)) { 277 throw new TargetSetupError( 278 "Cannot find image zip in build info.", 279 getDeviceDescriptor(), 280 InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND); 281 } 282 // TODO(b/240589011): Remove the build info keys after the config files are updated. 283 mBootImageDir = findAndExtractFile(info, BOOT_IMAGE_ZIP_NAME); 284 mSystemImageDir = findAndExtractFile(info, SYSTEM_IMAGE_ZIP_NAME); 285 mOtaToolsDir = findAndExtractFile(info, OTA_TOOLS_ZIP_NAME); 286 mInstanceDir = createTempDir(); 287 } catch (TargetSetupError ex) { 288 deleteTempDirs(); 289 throw ex; 290 } 291 if (mOtaToolsDir != null) { 292 FileUtil.chmodRWXRecursively(new File(mOtaToolsDir, "bin")); 293 } 294 if (mHostPackageDir != null) { 295 FileUtil.chmodRWXRecursively(new File(mHostPackageDir, "bin")); 296 } 297 if (fileMap.containsKey(ACLOUD_LOCAL_TOOL_OPTION)) { 298 for (File toolDir : fileMap.get(ACLOUD_LOCAL_TOOL_OPTION)) { 299 FileUtil.chmodRWXRecursively(new File(toolDir, "bin")); 300 } 301 } 302 } 303 resetAttributes()304 private void resetAttributes() { 305 mTempDirs.clear(); 306 mImageDir = null; 307 mInstanceDir = null; 308 mHostPackageDir = null; 309 mBootImageDir = null; 310 mSystemImageDir = null; 311 mOtaToolsDir = null; 312 mGceAvdInfo = null; 313 mCanShutdown = false; 314 } 315 316 /** Delete all temporary directories. */ 317 @VisibleForTesting deleteTempDirs()318 void deleteTempDirs() { 319 for (File tempDir : mTempDirs) { 320 FileUtil.recursiveDelete(tempDir); 321 } 322 mTempDirs.clear(); 323 } 324 325 /** 326 * Change the initial serial number of {@link StubLocalAndroidVirtualDevice}. 327 * 328 * @param newSerialNumber the serial number of the new stub device. 329 * @throws TargetSetupError if the original device type is not expected. 330 */ replaceStubDevice(String newSerialNumber)331 private void replaceStubDevice(String newSerialNumber) throws TargetSetupError { 332 IDevice device = getIDevice(); 333 if (!StubLocalAndroidVirtualDevice.class.equals(device.getClass())) { 334 throw new TargetSetupError( 335 "Unexpected device type: " + device.getClass(), 336 getDeviceDescriptor(), 337 InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); 338 } 339 setIDevice( 340 new StubLocalAndroidVirtualDevice( 341 newSerialNumber, 342 ((DefaultConnection) getConnection()).getInitialDeviceNumOffset())); 343 setFastbootEnabled(false); 344 } 345 346 /** Restore the {@link StubLocalAndroidVirtualDevice} with the initial serial number. */ restoreStubDevice()347 private void restoreStubDevice() { 348 setIDevice( 349 new StubLocalAndroidVirtualDevice( 350 ((DefaultConnection) getConnection()).getInitialSerial(), 351 ((DefaultConnection) getConnection()).getInitialDeviceNumOffset())); 352 setFastbootEnabled(false); 353 } 354 getAcloudFileArgs(MultiMap<String, File> fileMap)355 private List<String> getAcloudFileArgs(MultiMap<String, File> fileMap) { 356 List<String> args = new ArrayList<>(); 357 if (mImageDir != null) { 358 args.add("--" + ACLOUD_LOCAL_IMAGE_OPTION); 359 args.add(mImageDir.getAbsolutePath()); 360 } 361 if (mHostPackageDir != null) { 362 args.add("--" + ACLOUD_LOCAL_TOOL_OPTION); 363 args.add(mHostPackageDir.getAbsolutePath()); 364 } 365 if (mBootImageDir != null) { 366 args.add("--local-boot-image"); 367 args.add(mBootImageDir.getAbsolutePath()); 368 } 369 if (mSystemImageDir != null) { 370 args.add("--local-system-image"); 371 args.add(mSystemImageDir.getAbsolutePath()); 372 } 373 if (mOtaToolsDir != null) { 374 args.add("--local-tool"); 375 args.add(mOtaToolsDir.getAbsolutePath()); 376 } 377 for (Map.Entry<String, File> entry : fileMap.entries()) { 378 args.add("--" + entry.getKey()); 379 args.add(entry.getValue().getAbsolutePath()); 380 } 381 return args; 382 } 383 addLogLevelToAcloudCommand(List<String> command, LogLevel logLevel)384 private static void addLogLevelToAcloudCommand(List<String> command, LogLevel logLevel) { 385 if (LogLevel.VERBOSE.equals(logLevel)) { 386 command.add("-v"); 387 } else if (LogLevel.DEBUG.equals(logLevel)) { 388 command.add("-vv"); 389 } 390 } 391 acloudCreate(File report, TestDeviceOptions options)392 private CommandResult acloudCreate(File report, TestDeviceOptions options) { 393 CommandResult result = null; 394 395 File acloud = options.getAvdDriverBinary(); 396 if (acloud == null || !acloud.isFile()) { 397 CLog.e("Specified AVD driver binary is not a file."); 398 result = new CommandResult(CommandStatus.EXCEPTION); 399 result.setStderr("Specified AVD driver binary is not a file."); 400 return result; 401 } 402 acloud.setExecutable(true); 403 404 for (int attempt = 0; attempt < options.getGceMaxAttempt(); attempt++) { 405 result = 406 acloudCreate( 407 options.getGceCmdTimeout(), 408 acloud, 409 report, 410 options.getGceDriverLogLevel(), 411 options.getGceDriverFileParams(), 412 options.getGceDriverParams()); 413 if (CommandStatus.SUCCESS.equals(result.getStatus())) { 414 break; 415 } 416 CLog.w( 417 "Failed to start local virtual instance with attempt: %d; command status: %s", 418 attempt, result.getStatus()); 419 } 420 return result; 421 } 422 acloudCreate( long timeout, File acloud, File report, LogLevel logLevel, MultiMap<String, File> fileMap, List<String> args)423 private CommandResult acloudCreate( 424 long timeout, 425 File acloud, 426 File report, 427 LogLevel logLevel, 428 MultiMap<String, File> fileMap, 429 List<String> args) { 430 IRunUtil runUtil = createRunUtil(); 431 // The command creates files under TMPDIR. 432 runUtil.setEnvVariable( 433 TMPDIR, new File(System.getProperty("java.io.tmpdir")).getAbsolutePath()); 434 435 List<String> command = 436 new ArrayList<String>( 437 Arrays.asList( 438 acloud.getAbsolutePath(), 439 "create", 440 "--local-instance", 441 Integer.toString( 442 ((DefaultConnection) getConnection()) 443 .getInitialDeviceNumOffset() 444 + 1), 445 "--local-instance-dir", 446 mInstanceDir.getAbsolutePath(), 447 "--report_file", 448 report.getAbsolutePath(), 449 "--no-autoconnect", 450 "--yes", 451 "--skip-pre-run-check")); 452 addLogLevelToAcloudCommand(command, logLevel); 453 command.addAll(getAcloudFileArgs(fileMap)); 454 command.addAll(args); 455 456 mCanShutdown = true; 457 CommandResult result = runUtil.runTimedCmd(timeout, command.toArray(new String[0])); 458 CLog.i("acloud create stdout:\n%s", result.getStdout()); 459 CLog.i("acloud create stderr:\n%s", result.getStderr()); 460 return result; 461 } 462 463 /** 464 * Get valid host and port from mGceAvdInfo. 465 * 466 * @return {@link HostAndPort} if the port is valid; null otherwise. 467 */ getHostAndPortFromAvdInfo()468 private HostAndPort getHostAndPortFromAvdInfo() { 469 if (mGceAvdInfo == null) { 470 return null; 471 } 472 HostAndPort hostAndPort = mGceAvdInfo.hostAndPort(); 473 if (hostAndPort == null 474 || !hostAndPort.hasPort() 475 || hostAndPort.getPort() == INVALID_PORT) { 476 return null; 477 } 478 return hostAndPort; 479 } 480 481 /** Initialize instance name, host address, and port from an acloud report file. */ loadAvdInfo(File report)482 private void loadAvdInfo(File report) throws TargetSetupError { 483 mGceAvdInfo = GceAvdInfo.parseGceInfoFromFile(report, getDeviceDescriptor(), INVALID_PORT); 484 if (mGceAvdInfo == null) { 485 throw new TargetSetupError( 486 "Cannot read acloud report file.", 487 getDeviceDescriptor(), 488 InfraErrorIdentifier.NO_ACLOUD_REPORT); 489 } 490 491 if (!GceAvdInfo.GceStatus.SUCCESS.equals(mGceAvdInfo.getStatus())) { 492 throw new TargetSetupError( 493 "Cannot launch virtual device: " + mGceAvdInfo.getErrors(), 494 getDeviceDescriptor(), 495 mGceAvdInfo.getErrorType()); 496 } 497 498 if (Strings.isNullOrEmpty(mGceAvdInfo.instanceName())) { 499 throw new TargetSetupError( 500 "No instance name in acloud report.", 501 getDeviceDescriptor(), 502 InfraErrorIdentifier.NO_ACLOUD_REPORT); 503 } 504 505 if (getHostAndPortFromAvdInfo() == null) { 506 throw new TargetSetupError( 507 "No port in acloud report.", 508 getDeviceDescriptor(), 509 InfraErrorIdentifier.NO_ACLOUD_REPORT); 510 } 511 } 512 513 /** Shutdown the device. */ shutdown()514 public synchronized void shutdown() { 515 TestDeviceOptions options = getOptions(); 516 if (!mCanShutdown || options.shouldSkipTearDown()) { 517 CLog.i("Skip shutting down the virtual device."); 518 return; 519 } 520 // After this device is shut down, the resources like network ports and instance name may 521 // be reused by other devices. Hence, this device must not be shut down more than once. 522 mCanShutdown = false; 523 524 HostAndPort hostAndPort = getHostAndPortFromAvdInfo(); 525 String instanceName = (mGceAvdInfo != null ? mGceAvdInfo.instanceName() : null); 526 527 if (hostAndPort != null) { 528 if (!adbTcpDisconnect(hostAndPort.getHost(), Integer.toString(hostAndPort.getPort()))) { 529 CLog.e("Cannot disconnect from %s", hostAndPort.toString()); 530 } 531 } else { 532 CLog.i("Skip disconnecting."); 533 } 534 535 if (instanceName != null) { 536 CommandResult result = acloudDelete(instanceName, options); 537 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 538 CLog.e("Cannot stop the virtual device."); 539 } 540 } else { 541 CLog.i("Skip acloud delete."); 542 } 543 } 544 acloudDelete(String instanceName, TestDeviceOptions options)545 private CommandResult acloudDelete(String instanceName, TestDeviceOptions options) { 546 File acloud = options.getAvdDriverBinary(); 547 if (acloud == null || !acloud.isFile()) { 548 CLog.e("Specified AVD driver binary is not a file."); 549 return new CommandResult(CommandStatus.EXCEPTION); 550 } 551 acloud.setExecutable(true); 552 553 IRunUtil runUtil = createRunUtil(); 554 runUtil.setEnvVariable( 555 TMPDIR, new File(System.getProperty("java.io.tmpdir")).getAbsolutePath()); 556 557 List<String> command = 558 new ArrayList<String>( 559 Arrays.asList( 560 acloud.getAbsolutePath(), 561 "delete", 562 "--local-only", 563 "--instance-names", 564 instanceName)); 565 addLogLevelToAcloudCommand(command, options.getGceDriverLogLevel()); 566 567 CommandResult result = 568 runUtil.runTimedCmd(options.getGceCmdTimeout(), command.toArray(new String[0])); 569 CLog.i("acloud delete stdout:\n%s", result.getStdout()); 570 CLog.i("acloud delete stderr:\n%s", result.getStderr()); 571 return result; 572 } 573 reportInstanceLogs()574 private void reportInstanceLogs() { 575 if (mTestLogger == null || mInstanceDir == null || mGceAvdInfo == null) { 576 return; 577 } 578 Path realInstanceDir = null; 579 try { 580 realInstanceDir = mInstanceDir.toPath().toRealPath(); 581 } catch (IOException ex) { 582 CLog.e(ex); 583 return; 584 } 585 for (GceAvdInfo.LogFileEntry log : mGceAvdInfo.getLogs()) { 586 File file = new File(log.path); 587 if (file.exists()) { 588 try (InputStreamSource source = new FileInputStreamSource(file)) { 589 if (file.toPath().toRealPath().startsWith(realInstanceDir)) { 590 mTestLogger.testLog( 591 Strings.isNullOrEmpty(log.name) ? file.getName() : log.name, 592 log.type, 593 source); 594 } else { 595 CLog.w("%s is not in instance directory.", file.getAbsolutePath()); 596 } 597 } catch (IOException ex) { 598 CLog.e(ex); 599 } 600 } else { 601 CLog.w("%s doesn't exist.", file.getAbsolutePath()); 602 } 603 } 604 } 605 adbTcpConnect(String host, String port)606 public boolean adbTcpConnect(String host, String port) { 607 AdbTcpConnection conn = 608 new AdbTcpConnection(new ConnectionBuilder(getRunUtil(), this, null, mTestLogger)); 609 return conn.adbTcpConnect(host, port); 610 } 611 adbTcpDisconnect(String host, String port)612 public boolean adbTcpDisconnect(String host, String port) { 613 AdbTcpConnection conn = 614 new AdbTcpConnection(new ConnectionBuilder(getRunUtil(), this, null, mTestLogger)); 615 return conn.adbTcpDisconnect(host, port); 616 } 617 } 618