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