1#!/usr/bin/env python3.4 2# 3# Copyright 2016 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from builtins import str 18 19import random 20import socket 21import subprocess 22import time 23 24class AdbError(Exception): 25 """Raised when there is an error in adb operations.""" 26 27SL4A_LAUNCH_CMD=("am start -a com.googlecode.android_scripting.action.LAUNCH_SERVER " 28 "--ei com.googlecode.android_scripting.extra.USE_SERVICE_PORT {} " 29 "com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher" ) 30 31def get_available_host_port(): 32 """Gets a host port number available for adb forward. 33 34 Returns: 35 An integer representing a port number on the host available for adb 36 forward. 37 """ 38 while True: 39 port = random.randint(1024, 9900) 40 if is_port_available(port): 41 return port 42 43def is_port_available(port): 44 """Checks if a given port number is available on the system. 45 46 Args: 47 port: An integer which is the port number to check. 48 49 Returns: 50 True if the port is available; False otherwise. 51 """ 52 # Make sure adb is not using this port so we don't accidentally interrupt 53 # ongoing runs by trying to bind to the port. 54 if port in list_occupied_adb_ports(): 55 return False 56 s = None 57 try: 58 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 59 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 60 s.bind(('localhost', port)) 61 return True 62 except socket.error: 63 return False 64 finally: 65 if s: 66 s.close() 67 68def list_occupied_adb_ports(): 69 """Lists all the host ports occupied by adb forward. 70 71 This is useful because adb will silently override the binding if an attempt 72 to bind to a port already used by adb was made, instead of throwing binding 73 error. So one should always check what ports adb is using before trying to 74 bind to a port with adb. 75 76 Returns: 77 A list of integers representing occupied host ports. 78 """ 79 out = AdbProxy().forward("--list") 80 clean_lines = str(out, 'utf-8').strip().split('\n') 81 used_ports = [] 82 for line in clean_lines: 83 tokens = line.split(" tcp:") 84 if len(tokens) != 3: 85 continue 86 used_ports.append(int(tokens[1])) 87 return used_ports 88 89class AdbProxy(): 90 """Proxy class for ADB. 91 92 For syntactic reasons, the '-' in adb commands need to be replaced with 93 '_'. Can directly execute adb commands on an object: 94 >> adb = AdbProxy(<serial>) 95 >> adb.start_server() 96 >> adb.devices() # will return the console output of "adb devices". 97 """ 98 def __init__(self, serial="", log=None): 99 self.serial = serial 100 if serial: 101 self.adb_str = "adb -s {}".format(serial) 102 else: 103 self.adb_str = "adb" 104 self.log = log 105 106 def _exec_cmd(self, cmd): 107 """Executes adb commands in a new shell. 108 109 This is specific to executing adb binary because stderr is not a good 110 indicator of cmd execution status. 111 112 Args: 113 cmds: A string that is the adb command to execute. 114 115 Returns: 116 The output of the adb command run if exit code is 0. 117 118 Raises: 119 AdbError is raised if the adb command exit code is not 0. 120 """ 121 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) 122 (out, err) = proc.communicate() 123 ret = proc.returncode 124 total_output = "stdout: {}, stderr: {}, ret: {}".format(out, err, ret) 125 # TODO(angli): Fix this when global logger is done. 126 if self.log: 127 self.log.debug("{}\n{}".format(cmd, total_output)) 128 if ret == 0: 129 return out 130 else: 131 raise AdbError(total_output) 132 133 def _exec_adb_cmd(self, name, arg_str): 134 return self._exec_cmd(' '.join((self.adb_str, name, arg_str))) 135 136 def tcp_forward(self, host_port, device_port): 137 """Starts tcp forwarding. 138 139 Args: 140 host_port: Port number to use on the computer. 141 device_port: Port number to use on the android device. 142 """ 143 self.forward("tcp:{} tcp:{}".format(host_port, device_port)) 144 145 def start_sl4a(self, port=8080): 146 """Starts sl4a server on the android device. 147 148 Args: 149 port: Port number to use on the android device. 150 """ 151 MAX_SL4A_WAIT_TIME = 10 152 print(self.shell(SL4A_LAUNCH_CMD.format(port))) 153 154 for _ in range(MAX_SL4A_WAIT_TIME): 155 time.sleep(1) 156 if self.is_sl4a_running(): 157 return 158 raise AdbError( 159 "com.googlecode.android_scripting process never started.") 160 161 def is_sl4a_running(self): 162 """Checks if the sl4a app is running on an android device. 163 164 Returns: 165 True if the sl4a app is running, False otherwise. 166 """ 167 #Grep for process with a preceding S which means it is truly started. 168 out = self.shell('ps | grep "S com.googlecode.android_scripting"') 169 if len(out)==0: 170 return False 171 return True 172 173 def __getattr__(self, name): 174 def adb_call(*args): 175 clean_name = name.replace('_', '-') 176 arg_str = ' '.join(str(elem) for elem in args) 177 return self._exec_adb_cmd(clean_name, arg_str) 178 return adb_call 179