• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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