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