blob: c6a0e96ffb83485ce33356c5e94011484812d90d [file] [log] [blame]
Ang Li93420002016-05-10 19:11:44 -07001#!/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