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.log.LogUtil.CLog; 20 import com.android.tradefed.util.CommandResult; 21 import com.android.tradefed.util.CommandStatus; 22 23 /** 24 * Implementation of a {@link ITestDevice} for a full stack android device connected via 25 * adb connect. 26 * Assume the device serial will be in the format <hostname>:<portnumber> in adb. 27 */ 28 public class RemoteAndroidDevice extends TestDevice { 29 public static final long WAIT_FOR_ADB_CONNECT = 2 * 60 * 1000; 30 31 protected static final long RETRY_INTERVAL_MS = 5000; 32 protected static final int MAX_RETRIES = 5; 33 protected static final long DEFAULT_SHORT_CMD_TIMEOUT = 20 * 1000; 34 35 private static final String ADB_SUCCESS_CONNECT_TAG = "connected to"; 36 private static final String ADB_ALREADY_CONNECTED_TAG = "already"; 37 private static final String ADB_CONN_REFUSED = "Connection refused"; 38 39 /** 40 * Creates a {@link RemoteAndroidDevice}. 41 * 42 * @param device the associated {@link IDevice} 43 * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use 44 * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes. 45 */ 46 public RemoteAndroidDevice(IDevice device, IDeviceStateMonitor stateMonitor, 47 IDeviceMonitor allocationMonitor) { 48 super(device, stateMonitor, allocationMonitor); 49 } 50 51 /** 52 * {@inheritDoc} 53 */ 54 @Override 55 public void postAdbRootAction() throws DeviceNotAvailableException { 56 // attempt to reconnect first to make sure we didn't loose the connection because of 57 // adb root. 58 adbTcpConnect(getHostName(), getPortNum()); 59 waitForAdbConnect(WAIT_FOR_ADB_CONNECT); 60 } 61 62 /** 63 * {@inheritDoc} 64 */ 65 @Override 66 public void postAdbUnrootAction() throws DeviceNotAvailableException { 67 // attempt to reconnect first to make sure we didn't loose the connection because of 68 // adb unroot. 69 adbTcpConnect(getHostName(), getPortNum()); 70 waitForAdbConnect(WAIT_FOR_ADB_CONNECT); 71 } 72 73 /** 74 * Return the hostname associated with the device. Extracted from the serial. 75 */ 76 public String getHostName() { 77 if (!checkSerialFormatValid()) { 78 throw new RuntimeException( 79 String.format("Serial Format is unexpected: %s " 80 + "should look like <hostname>:<port>", getSerialNumber())); 81 } 82 return getSerialNumber().split(":")[0]; 83 } 84 85 /** 86 * Return the port number asociated with the device. Extracted from the serial. 87 */ 88 public String getPortNum() { 89 if (!checkSerialFormatValid()) { 90 throw new RuntimeException( 91 String.format("Serial Format is unexpected: %s " 92 + "should look like <hostname>:<port>", getSerialNumber())); 93 } 94 return getSerialNumber().split(":")[1]; 95 } 96 97 /** 98 * Check if the format of the serial is as expected <hostname>:port 99 * @return true if the format is valid, false otherwise. 100 */ 101 private boolean checkSerialFormatValid() { 102 String[] serial = getSerialNumber().split(":"); 103 if (serial.length == 2) { 104 try { 105 Integer.parseInt(serial[1]); 106 return true; 107 } catch (NumberFormatException nfe) { 108 return false; 109 } 110 } 111 return false; 112 } 113 114 /** 115 * Helper method to adb connect to a given tcp ip Android device 116 * 117 * @param host the hostname/ip of a tcp/ip Android device 118 * @param port the port number of a tcp/ip device 119 * @return true if we successfully connected to the device, false 120 * otherwise. 121 */ 122 public boolean adbTcpConnect(String host, String port) { 123 for (int i = 0; i < MAX_RETRIES; i++) { 124 CommandResult result = getRunUtil().runTimedCmd(DEFAULT_SHORT_CMD_TIMEOUT, "adb", 125 "connect", String.format("%s:%s", host, port)); 126 if (CommandStatus.SUCCESS.equals(result.getStatus()) && 127 result.getStdout().contains(ADB_SUCCESS_CONNECT_TAG)) { 128 CLog.d("adb connect output: status: %s stdout: %s stderr: %s", 129 result.getStatus(), result.getStdout(), result.getStderr()); 130 131 // It is possible to get a positive result without it being connected because of 132 // the ssh bridge. Retrying to get confirmation, and expecting "already connected". 133 if(confirmAdbTcpConnect(host, port)) { 134 return true; 135 } 136 } else if (CommandStatus.SUCCESS.equals(result.getStatus()) && 137 result.getStdout().contains(ADB_CONN_REFUSED)) { 138 // If we find "Connection Refused", we bail out directly as more connect won't help 139 return false; 140 } 141 CLog.d("adb connect output: status: %s stdout: %s stderr: %s, retrying.", 142 result.getStatus(), result.getStdout(), result.getStderr()); 143 getRunUtil().sleep((i + 1) * RETRY_INTERVAL_MS); 144 } 145 return false; 146 } 147 148 private boolean confirmAdbTcpConnect(String host, String port) { 149 CommandResult resultConfirmation = 150 getRunUtil().runTimedCmd(DEFAULT_SHORT_CMD_TIMEOUT, "adb", "connect", 151 String.format("%s:%s", host, port)); 152 if (CommandStatus.SUCCESS.equals(resultConfirmation.getStatus()) && 153 resultConfirmation.getStdout().contains(ADB_ALREADY_CONNECTED_TAG)) { 154 CLog.d("adb connect confirmed:\nstdout: %s\nsterr: %s", 155 resultConfirmation.getStdout(), resultConfirmation.getStderr()); 156 return true; 157 } else { 158 CLog.d("adb connect confirmation failed:\nstatus:%s\nstdout: %s\nsterr: %s", 159 resultConfirmation.getStatus(), resultConfirmation.getStdout(), 160 resultConfirmation.getStderr()); 161 } 162 return false; 163 } 164 165 /** 166 * Helper method to adb disconnect from a given tcp ip Android device 167 * 168 * @param host the hostname/ip of a tcp/ip Android device 169 * @param port the port number of a tcp/ip device 170 * @return true if we successfully disconnected to the device, false 171 * otherwise. 172 */ 173 public boolean adbTcpDisconnect(String host, String port) { 174 CommandResult result = getRunUtil().runTimedCmd(DEFAULT_SHORT_CMD_TIMEOUT, "adb", 175 "disconnect", 176 String.format("%s:%s", host, port)); 177 return CommandStatus.SUCCESS.equals(result.getStatus()); 178 } 179 180 /** 181 * Check if the adb connection is enabled. 182 */ 183 public void waitForAdbConnect(final long waitTime) throws DeviceNotAvailableException { 184 CLog.i("Waiting %d ms for adb connection.", waitTime); 185 long startTime = System.currentTimeMillis(); 186 while (System.currentTimeMillis() - startTime < waitTime) { 187 if (confirmAdbTcpConnect(getHostName(), getPortNum())) { 188 CLog.d("Adb connection confirmed."); 189 return; 190 } 191 getRunUtil().sleep(RETRY_INTERVAL_MS); 192 } 193 throw new DeviceNotAvailableException( 194 String.format("No adb connection after %sms.", waitTime), getSerialNumber()); 195 } 196 197 /** 198 * {@inheritDoc} 199 */ 200 @Override 201 public boolean isEncryptionSupported() { 202 // Prevent device from being encrypted since we won't have a way to decrypt on Remote 203 // devices since fastboot cannot be use remotely 204 return false; 205 } 206 207 /** 208 * {@inheritDoc} 209 */ 210 @Override 211 public String getMacAddress() { 212 return null; 213 } 214 } 215