1# Copyright 2019 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""A tool that help to run adb to check device status.""" 15 16import re 17import subprocess 18 19from acloud import errors 20from acloud.internal import constants 21from acloud.internal.lib import utils 22 23_ADB_CONNECT = "connect" 24_ADB_DEVICE = "devices" 25_ADB_DISCONNECT = "disconnect" 26_ADB_STATUS_DEVICE = "device" 27_ADB_STATUS_DEVICE_ARGS = "-l" 28_RE_ADB_DEVICE_INFO = (r"%s\s*(?P<adb_status>[\S]+)? ?" 29 r"(usb:(?P<usb>[\S]+))? ?" 30 r"(product:(?P<product>[\S]+))? ?" 31 r"(model:(?P<model>[\S]+))? ?" 32 r"(device:(?P<device>[\S]+))? ?" 33 r"(transport_id:(?P<transport_id>[\S]+))? ?") 34_DEVICE_ATTRIBUTES = ["adb_status", "usb", "product", "model", "device", "transport_id"] 35_MAX_RETRIES_ON_WAIT_ADB_GONE = 5 36#KEY_CODE 82 = KEY_MENU 37_UNLOCK_SCREEN_KEYEVENT = ("%(adb_bin)s -s %(device_serial)s " 38 "shell input keyevent 82") 39_WAIT_ADB_RETRY_BACKOFF_FACTOR = 1.5 40_WAIT_ADB_SLEEP_MULTIPLIER = 2 41 42 43class AdbTools: 44 """Adb tools. 45 46 Attributes: 47 _adb_command: String, combine adb commands then execute it. 48 _adb_port: Integer, Specified adb port to establish connection. 49 _device_address: String, the device's host and port for adb to connect 50 to. For example, adb connect 127.0.0.1:5555. 51 _device_serial: String, adb device's serial number. The value can be 52 different from _device_address. For example, 53 adb -s emulator-5554 shell. 54 _device_information: Dict, will be added to adb information include usb, 55 product model, device and transport_id 56 """ 57 _adb_command = None 58 59 def __init__(self, adb_port=None, device_serial=""): 60 """Initialize. 61 62 Args: 63 adb_port: String of adb port number. 64 device_serial: String, adb device's serial number. 65 """ 66 self._adb_port = adb_port 67 self._device_address = "" 68 self._device_serial = "" 69 self._SetDeviceSerial(device_serial) 70 self._device_information = {} 71 self._CheckAdb() 72 self._GetAdbInformation() 73 74 def _SetDeviceSerial(self, device_serial): 75 """Set device serial and address. 76 77 Args: 78 device_serial: String, the device's serial number. If this 79 argument is empty, the serial number is set to the 80 network address. 81 """ 82 self._device_address = ("127.0.0.1:%s" % self._adb_port if 83 self._adb_port else "") 84 self._device_serial = (device_serial if device_serial else 85 self._device_address) 86 87 @classmethod 88 def _CheckAdb(cls): 89 """Find adb bin path. 90 91 Raises: 92 errors.NoExecuteCmd: Can't find the execute adb bin. 93 """ 94 if cls._adb_command: 95 return 96 cls._adb_command = utils.FindExecutable(constants.ADB_BIN) 97 if not cls._adb_command: 98 raise errors.NoExecuteCmd("Can't find the adb command.") 99 100 def GetAdbConnectionStatus(self): 101 """Get Adb connect status. 102 103 Check if self._adb_port is null (ssh tunnel is broken). 104 105 Returns: 106 String, the result of adb connection. 107 """ 108 if not self._adb_port: 109 return None 110 111 return self._device_information["adb_status"] 112 113 def _GetAdbInformation(self): 114 """Get Adb connect information. 115 116 1. Check adb devices command to get the connection information. 117 118 2. Gather information include usb, product model, device and transport_id 119 when the attached field is device. 120 121 e.g. 122 Case 1: 123 List of devices attached 124 127.0.0.1:48451 device product:aosp_cf model:Cuttlefish device:vsoc_x86 transport_id:147 125 _device_information = {"adb_status":"device", 126 "usb":None, 127 "product":"aosp_cf", 128 "model":"Cuttlefish", 129 "device":"vsoc_x86", 130 "transport_id":"147"} 131 132 Case 2: 133 List of devices attached 134 127.0.0.1:48451 offline 135 _device_information = {"adb_status":"offline", 136 "usb":None, 137 "product":None, 138 "model":None, 139 "device":None, 140 "transport_id":None} 141 142 Case 3: 143 List of devices attached 144 _device_information = {"adb_status":None, 145 "usb":None, 146 "product":None, 147 "model":None, 148 "device":None, 149 "transport_id":None} 150 """ 151 adb_cmd = [self._adb_command, _ADB_DEVICE, _ADB_STATUS_DEVICE_ARGS] 152 device_info = utils.CheckOutput(adb_cmd) 153 self._device_information = { 154 attribute: None for attribute in _DEVICE_ATTRIBUTES} 155 156 for device in device_info.splitlines(): 157 match = re.match(_RE_ADB_DEVICE_INFO % self._device_serial, device) 158 if match: 159 self._device_information = { 160 attribute: match.group(attribute) if match.group(attribute) 161 else None for attribute in _DEVICE_ATTRIBUTES} 162 163 @classmethod 164 def GetDeviceSerials(cls): 165 """Get the serial numbers of connected devices.""" 166 cls._CheckAdb() 167 adb_cmd = [cls._adb_command, _ADB_DEVICE] 168 device_info = utils.CheckOutput(adb_cmd) 169 serials = [] 170 # Skip the first line which is "List of devices attached". Each of the 171 # following lines consists of the serial number, a tab character, and 172 # the state. The last line is empty. 173 for line in device_info.splitlines()[1:]: 174 serial_state = line.split() 175 if len(serial_state) > 1: 176 serials.append(serial_state[0]) 177 return serials 178 179 def IsAdbConnectionAlive(self): 180 """Check devices connect alive. 181 182 Returns: 183 Boolean, True if adb status is device. False otherwise. 184 """ 185 return self.GetAdbConnectionStatus() == _ADB_STATUS_DEVICE 186 187 def IsAdbConnected(self): 188 """Check devices connected or not. 189 190 If adb connected and the status is device or offline, return True. 191 If there is no any connection, return False. 192 193 Returns: 194 Boolean, True if adb status not none. False otherwise. 195 """ 196 return self.GetAdbConnectionStatus() is not None 197 198 def _DisconnectAndRaiseError(self): 199 """Disconnect adb. 200 201 Disconnect from the device's network address if it shows up in adb 202 devices. For example, adb disconnect 127.0.0.1:5555. 203 204 Raises: 205 errors.WaitForAdbDieError: adb is alive after disconnect adb. 206 """ 207 try: 208 if self.IsAdbConnected(): 209 adb_disconnect_args = [self._adb_command, 210 _ADB_DISCONNECT, 211 self._device_address] 212 subprocess.check_call(adb_disconnect_args) 213 # check adb device status 214 self._GetAdbInformation() 215 if self.IsAdbConnected(): 216 raise errors.AdbDisconnectFailed( 217 "adb disconnect failed, device is still connected and " 218 "has status: [%s]" % self.GetAdbConnectionStatus()) 219 220 except subprocess.CalledProcessError: 221 utils.PrintColorString("Failed to adb disconnect %s" % 222 self._device_address, 223 utils.TextColors.FAIL) 224 225 def DisconnectAdb(self, retry=False): 226 """Retry to disconnect adb. 227 228 When retry=True, this method will retry to disconnect adb until adb 229 device is completely gone. 230 231 Args: 232 retry: Boolean, True to retry disconnect on error. 233 """ 234 retry_count = _MAX_RETRIES_ON_WAIT_ADB_GONE if retry else 0 235 # Wait for adb device is reset and gone. 236 utils.RetryExceptionType(exception_types=errors.AdbDisconnectFailed, 237 max_retries=retry_count, 238 functor=self._DisconnectAndRaiseError, 239 sleep_multiplier=_WAIT_ADB_SLEEP_MULTIPLIER, 240 retry_backoff_factor= 241 _WAIT_ADB_RETRY_BACKOFF_FACTOR) 242 243 def ConnectAdb(self): 244 """Connect adb. 245 246 Connect adb to the device's network address if the connection is not 247 alive. For example, adb connect 127.0.0.1:5555. 248 """ 249 try: 250 if not self.IsAdbConnectionAlive(): 251 adb_connect_args = [self._adb_command, 252 _ADB_CONNECT, 253 self._device_address] 254 subprocess.check_call(adb_connect_args) 255 except subprocess.CalledProcessError: 256 utils.PrintColorString("Failed to adb connect %s" % 257 self._device_address, 258 utils.TextColors.FAIL) 259 260 def AutoUnlockScreen(self): 261 """Auto unlock screen. 262 263 Auto unlock screen after invoke vnc client. 264 """ 265 try: 266 adb_unlock_args = _UNLOCK_SCREEN_KEYEVENT % { 267 "adb_bin": self._adb_command, 268 "device_serial": self._device_serial} 269 subprocess.check_call(adb_unlock_args.split()) 270 except subprocess.CalledProcessError: 271 utils.PrintColorString("Failed to unlock screen." 272 "(adb_port: %s)" % self._adb_port, 273 utils.TextColors.WARNING) 274 275 def EmuCommand(self, *args): 276 """Send an emulator command to the device. 277 278 Args: 279 args: List of strings, the emulator command. 280 281 Returns: 282 Integer, the return code of the adb command. 283 The return code is 0 if adb successfully sends the command to 284 emulator. It is irrelevant to the result of the command. 285 """ 286 adb_cmd = [self._adb_command, "-s", self._device_serial, "emu"] 287 adb_cmd.extend(args) 288 proc = subprocess.Popen(adb_cmd, stdin=subprocess.PIPE, 289 stdout=subprocess.PIPE, 290 stderr=subprocess.PIPE) 291 proc.communicate() 292 return proc.returncode 293 294 @property 295 def device_information(self): 296 """Return the device information.""" 297 return self._device_information 298