• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 package com.android.tradefed.device;
17 
18 import com.android.ddmlib.IDevice;
19 import com.android.tradefed.device.TestDeviceOptions.InstanceType;
20 import com.android.tradefed.log.LogUtil.CLog;
21 import com.android.tradefed.util.CommandResult;
22 import com.android.tradefed.util.CommandStatus;
23 import com.android.tradefed.util.FileUtil;
24 import com.android.tradefed.util.IRunUtil;
25 import com.android.tradefed.util.RunUtil;
26 
27 import java.io.File;
28 import java.io.IOException;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 
32 /**
33  * Implementation of a {@link ITestDevice} for a full stack android device connected via
34  * adb connect.
35  * Assume the device serial will be in the format <hostname>:<portnumber> in adb.
36  */
37 public class RemoteAndroidDevice extends TestDevice {
38     public static final long WAIT_FOR_ADB_CONNECT = 2 * 60 * 1000;
39 
40     protected static final long RETRY_INTERVAL_MS = 5000;
41     protected static final int MAX_RETRIES = 5;
42     protected static final long DEFAULT_SHORT_CMD_TIMEOUT = 20 * 1000;
43 
44     private static final String ADB_SUCCESS_CONNECT_TAG = "connected to";
45     private static final String ADB_ALREADY_CONNECTED_TAG = "already";
46     private static final String ADB_CONN_REFUSED = "Connection refused";
47 
48     private static final Pattern IP_PATTERN =
49             Pattern.compile(ManagedTestDeviceFactory.IPADDRESS_PATTERN);
50 
51     private File mAdbConnectLogs = null;
52 
53     /**
54      * Creates a {@link RemoteAndroidDevice}.
55      *
56      * @param device the associated {@link IDevice}
57      * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use
58      * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes.
59      */
RemoteAndroidDevice(IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor)60     public RemoteAndroidDevice(IDevice device, IDeviceStateMonitor stateMonitor,
61             IDeviceMonitor allocationMonitor) {
62         super(device, stateMonitor, allocationMonitor);
63     }
64 
65     @Override
postInvocationTearDown()66     public void postInvocationTearDown() {
67         super.postInvocationTearDown();
68         FileUtil.deleteFile(mAdbConnectLogs);
69     }
70 
71     /**
72      * {@inheritDoc}
73      */
74     @Override
postAdbRootAction()75     public void postAdbRootAction() throws DeviceNotAvailableException {
76         // attempt to reconnect first to make sure we didn't loose the connection because of
77         // adb root.
78         adbTcpConnect(getHostName(), getPortNum());
79         waitForAdbConnect(WAIT_FOR_ADB_CONNECT);
80     }
81 
82     /**
83      * {@inheritDoc}
84      */
85     @Override
postAdbUnrootAction()86     public void postAdbUnrootAction() throws DeviceNotAvailableException {
87         // attempt to reconnect first to make sure we didn't loose the connection because of
88         // adb unroot.
89         adbTcpConnect(getHostName(), getPortNum());
90         waitForAdbConnect(WAIT_FOR_ADB_CONNECT);
91     }
92 
93     /** {@inheritDoc} */
94     @Override
postAdbReboot()95     protected void postAdbReboot() throws DeviceNotAvailableException {
96         super.postAdbReboot();
97         // A remote nested device does not loose the ssh bridge when rebooted only adb connect is
98         // required.
99         InstanceType type = mOptions.getInstanceType();
100         if (InstanceType.CUTTLEFISH.equals(type)
101                 || InstanceType.REMOTE_NESTED_AVD.equals(type)
102                 || InstanceType.EMULATOR.equals(type)) {
103             adbTcpConnect(getHostName(), getPortNum());
104             waitForAdbConnect(WAIT_FOR_ADB_CONNECT);
105         }
106     }
107 
108     /** {@inheritDoc} */
109     @Override
recoverDevice()110     public void recoverDevice() throws DeviceNotAvailableException {
111         // If device is not in use (TcpDevice) do not attempt reconnection, it will fail
112         // device in use will not be of the TcpDevice type.
113         if (!(getIDevice() instanceof TcpDevice)) {
114             // Before attempting standard recovery, reconnect the device.
115             adbTcpConnect(getHostName(), getPortNum());
116             waitForAdbConnect(WAIT_FOR_ADB_CONNECT);
117         }
118         // Standard recovery
119         super.recoverDevice();
120     }
121 
122     /**
123      * Return the hostname associated with the device. Extracted from the serial.
124      */
getHostName()125     public String getHostName() {
126         if (!checkSerialFormatValid(getSerialNumber())) {
127             throw new RuntimeException(
128                     String.format("Serial Format is unexpected: %s "
129                             + "should look like <hostname>:<port>", getSerialNumber()));
130         }
131         return getSerialNumber().split(":")[0];
132     }
133 
134     /**
135      * Return the port number asociated with the device. Extracted from the serial.
136      */
getPortNum()137     public String getPortNum() {
138         if (!checkSerialFormatValid(getSerialNumber())) {
139             throw new RuntimeException(
140                     String.format("Serial Format is unexpected: %s "
141                             + "should look like <hostname>:<port>", getSerialNumber()));
142         }
143         return getSerialNumber().split(":")[1];
144     }
145 
146     /**
147      * Check if the format of the serial is as expected <hostname>:port
148      *
149      * @return true if the format is valid, false otherwise.
150      */
checkSerialFormatValid(String serialString)151     public static boolean checkSerialFormatValid(String serialString) {
152         String[] serial = serialString.split(":");
153         if (serial.length == 2) {
154             // Check first part is an IP
155             Matcher match = IP_PATTERN.matcher(serial[0]);
156             if (!match.find()) {
157                 return false;
158             }
159             // Check second part if a port
160             try {
161                 Integer.parseInt(serial[1]);
162                 return true;
163             } catch (NumberFormatException nfe) {
164                 return false;
165             }
166         }
167         return false;
168     }
169 
170     /**
171      * Helper method to adb connect to a given tcp ip Android device
172      *
173      * @param host the hostname/ip of a tcp/ip Android device
174      * @param port the port number of a tcp/ip device
175      * @return true if we successfully connected to the device, false
176      *         otherwise.
177      */
adbTcpConnect(String host, String port)178     public boolean adbTcpConnect(String host, String port) {
179         for (int i = 0; i < MAX_RETRIES; i++) {
180             CommandResult result = adbConnect(host, port);
181             if (CommandStatus.SUCCESS.equals(result.getStatus()) &&
182                 result.getStdout().contains(ADB_SUCCESS_CONNECT_TAG)) {
183                 CLog.d(
184                         "adb connect output: status: %s stdout: %s",
185                         result.getStatus(), result.getStdout());
186 
187                 // It is possible to get a positive result without it being connected because of
188                 // the ssh bridge. Retrying to get confirmation, and expecting "already connected".
189                 if(confirmAdbTcpConnect(host, port)) {
190                     return true;
191                 }
192             } else if (CommandStatus.SUCCESS.equals(result.getStatus()) &&
193                     result.getStdout().contains(ADB_CONN_REFUSED)) {
194                 // If we find "Connection Refused", we bail out directly as more connect won't help
195                 return false;
196             }
197             CLog.d("adb connect output: status: %s stdout: %s stderr: %s, retrying.",
198                     result.getStatus(), result.getStdout(), result.getStderr());
199             getRunUtil().sleep((i + 1) * RETRY_INTERVAL_MS);
200         }
201         return false;
202     }
203 
confirmAdbTcpConnect(String host, String port)204     private boolean confirmAdbTcpConnect(String host, String port) {
205         CommandResult resultConfirmation = adbConnect(host, port);
206         if (CommandStatus.SUCCESS.equals(resultConfirmation.getStatus())
207                 && resultConfirmation.getStdout().contains(ADB_ALREADY_CONNECTED_TAG)) {
208             CLog.d("adb connect confirmed:\nstdout: %s\n", resultConfirmation.getStdout());
209             return true;
210         } else {
211             CLog.d("adb connect confirmation failed:\nstatus:%s\nstdout: %s\nsterr: %s",
212                     resultConfirmation.getStatus(), resultConfirmation.getStdout(),
213                     resultConfirmation.getStderr());
214         }
215         return false;
216     }
217 
218     /**
219      * Helper method to adb disconnect from a given tcp ip Android device
220      *
221      * @param host the hostname/ip of a tcp/ip Android device
222      * @param port the port number of a tcp/ip device
223      * @return true if we successfully disconnected to the device, false
224      *         otherwise.
225      */
adbTcpDisconnect(String host, String port)226     public boolean adbTcpDisconnect(String host, String port) {
227         CommandResult result = getRunUtil().runTimedCmd(DEFAULT_SHORT_CMD_TIMEOUT, "adb",
228                 "disconnect",
229                 String.format("%s:%s", host, port));
230         return CommandStatus.SUCCESS.equals(result.getStatus());
231     }
232 
233     /**
234      * Check if the adb connection is enabled.
235      */
waitForAdbConnect(final long waitTime)236     public void waitForAdbConnect(final long waitTime) throws DeviceNotAvailableException {
237         CLog.i("Waiting %d ms for adb connection.", waitTime);
238         long startTime = System.currentTimeMillis();
239         while (System.currentTimeMillis() - startTime < waitTime) {
240             if (confirmAdbTcpConnect(getHostName(), getPortNum())) {
241                 CLog.d("Adb connection confirmed.");
242                 return;
243             }
244             getRunUtil().sleep(RETRY_INTERVAL_MS);
245         }
246         throw new DeviceNotAvailableException(
247                 String.format("No adb connection after %sms.", waitTime), getSerialNumber());
248     }
249 
250     /**
251      * {@inheritDoc}
252      */
253     @Override
isEncryptionSupported()254     public boolean isEncryptionSupported() {
255         // Prevent device from being encrypted since we won't have a way to decrypt on Remote
256         // devices since fastboot cannot be use remotely
257         return false;
258     }
259 
260     /**
261      * Give a receiver file where we can store all the adb connection logs for debugging purpose.
262      */
setAdbLogFile(File adbLogFile)263     public void setAdbLogFile(File adbLogFile) {
264         mAdbConnectLogs = adbLogFile;
265     }
266 
267     /**
268      * {@inheritDoc}
269      */
270     @Override
getMacAddress()271     public String getMacAddress() {
272         return null;
273     }
274 
275     /** Run adb connect. */
adbConnect(String host, String port)276     private CommandResult adbConnect(String host, String port) {
277         IRunUtil runUtil = getRunUtil();
278         if (mAdbConnectLogs != null) {
279             runUtil = new RunUtil();
280             runUtil.setEnvVariable("ADB_TRACE", "1");
281         }
282         CommandResult result =
283                 runUtil.runTimedCmd(
284                         DEFAULT_SHORT_CMD_TIMEOUT,
285                         "adb",
286                         "connect",
287                         String.format("%s:%s", host, port));
288         if (mAdbConnectLogs != null) {
289             try {
290                 FileUtil.writeToFile(result.getStderr(), mAdbConnectLogs, true);
291                 FileUtil.writeToFile(
292                         "\n======= SEPARATOR OF ATTEMPTS =====\n", mAdbConnectLogs, true);
293             } catch (IOException e) {
294                 CLog.e(e);
295             }
296         }
297         return result;
298     }
299 }
300