blob: 9e8859a9ad70691a87b0f4d06d9efbf46fbbd996 [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
Ang Lie2139f12016-05-12 17:39:06 -070024
Ang Li93420002016-05-10 19:11:44 -070025class AdbError(Exception):
26 """Raised when there is an error in adb operations."""
27
Ang Lie2139f12016-05-12 17:39:06 -070028
29SL4A_LAUNCH_CMD = (
30 "am start -a com.googlecode.android_scripting.action.LAUNCH_SERVER "
Ang Li93420002016-05-10 19:11:44 -070031 "--ei com.googlecode.android_scripting.extra.USE_SERVICE_PORT {} "
Ang Lie2139f12016-05-12 17:39:06 -070032 "com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher")
33
Ang Li93420002016-05-10 19:11:44 -070034
35def get_available_host_port():
36 """Gets a host port number available for adb forward.
37
38 Returns:
39 An integer representing a port number on the host available for adb
40 forward.
41 """
42 while True:
43 port = random.randint(1024, 9900)
44 if is_port_available(port):
45 return port
46
Ang Lie2139f12016-05-12 17:39:06 -070047
Ang Li93420002016-05-10 19:11:44 -070048def is_port_available(port):
49 """Checks if a given port number is available on the system.
50
51 Args:
52 port: An integer which is the port number to check.
53
54 Returns:
55 True if the port is available; False otherwise.
56 """
57 # Make sure adb is not using this port so we don't accidentally interrupt
58 # ongoing runs by trying to bind to the port.
59 if port in list_occupied_adb_ports():
60 return False
61 s = None
62 try:
63 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
64 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
65 s.bind(('localhost', port))
66 return True
67 except socket.error:
68 return False
69 finally:
70 if s:
71 s.close()
72
Ang Lie2139f12016-05-12 17:39:06 -070073
Ang Li93420002016-05-10 19:11:44 -070074def list_occupied_adb_ports():
75 """Lists all the host ports occupied by adb forward.
76
77 This is useful because adb will silently override the binding if an attempt
78 to bind to a port already used by adb was made, instead of throwing binding
79 error. So one should always check what ports adb is using before trying to
80 bind to a port with adb.
81
82 Returns:
83 A list of integers representing occupied host ports.
84 """
85 out = AdbProxy().forward("--list")
86 clean_lines = str(out, 'utf-8').strip().split('\n')
87 used_ports = []
88 for line in clean_lines:
89 tokens = line.split(" tcp:")
90 if len(tokens) != 3:
91 continue
92 used_ports.append(int(tokens[1]))
93 return used_ports
94
Ang Lie2139f12016-05-12 17:39:06 -070095
Ang Li93420002016-05-10 19:11:44 -070096class AdbProxy():
97 """Proxy class for ADB.
98
99 For syntactic reasons, the '-' in adb commands need to be replaced with
100 '_'. Can directly execute adb commands on an object:
101 >> adb = AdbProxy(<serial>)
102 >> adb.start_server()
103 >> adb.devices() # will return the console output of "adb devices".
104 """
Ang Lie2139f12016-05-12 17:39:06 -0700105
Ang Li93420002016-05-10 19:11:44 -0700106 def __init__(self, serial="", log=None):
107 self.serial = serial
108 if serial:
109 self.adb_str = "adb -s {}".format(serial)
110 else:
111 self.adb_str = "adb"
112 self.log = log
113
114 def _exec_cmd(self, cmd):
115 """Executes adb commands in a new shell.
116
117 This is specific to executing adb binary because stderr is not a good
118 indicator of cmd execution status.
119
120 Args:
121 cmds: A string that is the adb command to execute.
122
123 Returns:
124 The output of the adb command run if exit code is 0.
125
126 Raises:
127 AdbError is raised if the adb command exit code is not 0.
128 """
129 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
130 (out, err) = proc.communicate()
131 ret = proc.returncode
132 total_output = "stdout: {}, stderr: {}, ret: {}".format(out, err, ret)
133 # TODO(angli): Fix this when global logger is done.
134 if self.log:
135 self.log.debug("{}\n{}".format(cmd, total_output))
136 if ret == 0:
137 return out
138 else:
139 raise AdbError(total_output)
140
141 def _exec_adb_cmd(self, name, arg_str):
142 return self._exec_cmd(' '.join((self.adb_str, name, arg_str)))
143
144 def tcp_forward(self, host_port, device_port):
145 """Starts tcp forwarding.
146
147 Args:
148 host_port: Port number to use on the computer.
149 device_port: Port number to use on the android device.
150 """
151 self.forward("tcp:{} tcp:{}".format(host_port, device_port))
152
153 def start_sl4a(self, port=8080):
154 """Starts sl4a server on the android device.
155
156 Args:
157 port: Port number to use on the android device.
158 """
159 MAX_SL4A_WAIT_TIME = 10
160 print(self.shell(SL4A_LAUNCH_CMD.format(port)))
161
162 for _ in range(MAX_SL4A_WAIT_TIME):
163 time.sleep(1)
164 if self.is_sl4a_running():
165 return
166 raise AdbError(
Ang Lie2139f12016-05-12 17:39:06 -0700167 "com.googlecode.android_scripting process never started.")
Ang Li93420002016-05-10 19:11:44 -0700168
169 def is_sl4a_running(self):
170 """Checks if the sl4a app is running on an android device.
171
172 Returns:
173 True if the sl4a app is running, False otherwise.
174 """
175 #Grep for process with a preceding S which means it is truly started.
176 out = self.shell('ps | grep "S com.googlecode.android_scripting"')
Ang Lie2139f12016-05-12 17:39:06 -0700177 if len(out) == 0:
178 return False
Ang Li93420002016-05-10 19:11:44 -0700179 return True
180
181 def __getattr__(self, name):
182 def adb_call(*args):
183 clean_name = name.replace('_', '-')
184 arg_str = ' '.join(str(elem) for elem in args)
185 return self._exec_adb_cmd(clean_name, arg_str)
Ang Lie2139f12016-05-12 17:39:06 -0700186
Ang Li93420002016-05-10 19:11:44 -0700187 return adb_call