1 # 2 # Copyright 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 from builtins import str 17 18 import logging 19 import random 20 import socket 21 import subprocess 22 import time 23 24 from vts.runners.host import const 25 from vts.utils.python.common import cmd_utils 26 27 28 class AdbError(Exception): 29 """Raised when there is an error in adb operations.""" 30 31 def __init__(self, cmd, stdout, stderr, ret_code): 32 self.cmd = cmd 33 self.stdout = stdout 34 self.stderr = stderr 35 self.ret_code = ret_code 36 37 def __str__(self): 38 return ("Error executing adb cmd '%s'. ret: %d, stdout: %s, stderr: %s" 39 ) % (self.cmd, self.ret_code, self.stdout, self.stderr) 40 41 42 SL4A_LAUNCH_CMD = ( 43 "am start -a com.googlecode.android_scripting.action.LAUNCH_SERVER " 44 "--ei com.googlecode.android_scripting.extra.USE_SERVICE_PORT {} " 45 "com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher") 46 47 48 def get_available_host_port(): 49 """Gets a host port number available for adb forward. 50 51 Returns: 52 An integer representing a port number on the host available for adb 53 forward. 54 """ 55 while True: 56 port = random.randint(1024, 9900) 57 if is_port_available(port): 58 return port 59 60 61 def is_port_available(port): 62 """Checks if a given port number is available on the system. 63 64 Args: 65 port: An integer which is the port number to check. 66 67 Returns: 68 True if the port is available; False otherwise. 69 """ 70 # Make sure adb is not using this port so we don't accidentally interrupt 71 # ongoing runs by trying to bind to the port. 72 if port in list_occupied_adb_ports(): 73 return False 74 s = None 75 try: 76 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 77 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 78 s.bind(('localhost', port)) 79 return True 80 except socket.error: 81 return False 82 finally: 83 if s: 84 s.close() 85 86 87 def list_occupied_adb_ports(): 88 """Lists all the host ports occupied by adb forward. 89 90 This is useful because adb will silently override the binding if an attempt 91 to bind to a port already used by adb was made, instead of throwing binding 92 error. So one should always check what ports adb is using before trying to 93 bind to a port with adb. 94 95 Returns: 96 A list of integers representing occupied host ports. 97 """ 98 out = AdbProxy().forward("--list") 99 clean_lines = str(out, 'utf-8').strip().split('\n') 100 used_ports = [] 101 for line in clean_lines: 102 tokens = line.split(" tcp:") 103 if len(tokens) != 3: 104 continue 105 used_ports.append(int(tokens[1])) 106 return used_ports 107 108 109 class AdbProxy(): 110 """Proxy class for ADB. 111 112 For syntactic reasons, the '-' in adb commands need to be replaced with 113 '_'. Can directly execute adb commands on an object: 114 >> adb = AdbProxy(<serial>) 115 >> adb.start_server() 116 >> adb.devices() # will return the console output of "adb devices". 117 """ 118 119 def __init__(self, serial="", log=None): 120 self.serial = serial 121 if serial: 122 self.adb_str = "adb -s {}".format(serial) 123 else: 124 self.adb_str = "adb" 125 self.log = log 126 127 def _exec_cmd(self, cmd, no_except=False, timeout=None): 128 """Executes adb commands in a new shell. 129 130 This is specific to executing adb binary because stderr is not a good 131 indicator of cmd execution status. 132 133 Args: 134 cmd: string, the adb command to execute. 135 no_except: bool, controls whether exception can be thrown. 136 timeout: float, timeout in seconds. If the command times out, the 137 exit code is not 0. 138 139 Returns: 140 The output of the adb command run if the exit code is 0 and if 141 exceptions are allowed. Otherwise, returns a dictionary containing 142 stdout, stderr, and exit code. 143 144 Raises: 145 AdbError if the adb command exit code is not 0 and exceptions are 146 allowed. 147 """ 148 out, err, ret = cmd_utils.ExecuteOneShellCommand(cmd, timeout) 149 logging.debug("cmd: %s, stdout: %s, stderr: %s, ret: %s", cmd, out, 150 err, ret) 151 if no_except: 152 return { 153 const.STDOUT: out, 154 const.STDERR: err, 155 const.EXIT_CODE: ret, 156 } 157 else: 158 if ret == 0: 159 return out 160 else: 161 raise AdbError(cmd=cmd, stdout=out, stderr=err, ret_code=ret) 162 163 def tcp_forward(self, host_port, device_port): 164 """Starts TCP forwarding. 165 166 Args: 167 host_port: Port number to use on the computer. 168 device_port: Port number to use on the android device. 169 """ 170 self.forward("tcp:{} tcp:{}".format(host_port, device_port)) 171 172 def reverse_tcp_forward(self, device_port, host_port): 173 """Starts reverse TCP forwarding. 174 175 Args: 176 device_port: Port number to use on the android device. 177 host_port: Port number to use on the computer. 178 """ 179 self.reverse("tcp:{} tcp:{}".format(device_port, host_port)) 180 181 def __getattr__(self, name): 182 183 def adb_call(*args, **kwargs): 184 clean_name = name.replace('_', '-') 185 arg_str = ' '.join(str(elem) for elem in args) 186 return self._exec_cmd(' '.join((self.adb_str, clean_name, arg_str)), 187 **kwargs) 188 189 return adb_call 190