blob: c5b5eea4af31acdd43de174e96838508011d60b3 [file] [log] [blame]
Dan Albert8e1fdd72015-07-24 17:08:33 -07001#
2# Copyright (C) 2015 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#
Yasuhiro Matsudaab379832015-07-03 02:08:55 +090016import logging
Dan Albert8e1fdd72015-07-24 17:08:33 -070017import os
18import re
19import subprocess
Spencer Lowde4505f2015-08-27 20:58:29 -070020import tempfile
Dan Albert8e1fdd72015-07-24 17:08:33 -070021
22
23class FindDeviceError(RuntimeError):
24 pass
25
26
27class DeviceNotFoundError(FindDeviceError):
28 def __init__(self, serial):
29 self.serial = serial
30 super(DeviceNotFoundError, self).__init__(
31 'No device with serial {}'.format(serial))
32
33
34class NoUniqueDeviceError(FindDeviceError):
35 def __init__(self):
36 super(NoUniqueDeviceError, self).__init__('No unique device')
37
38
39def get_devices():
40 with open(os.devnull, 'wb') as devnull:
41 subprocess.check_call(['adb', 'start-server'], stdout=devnull,
42 stderr=devnull)
43 out = subprocess.check_output(['adb', 'devices']).splitlines()
44
45 # The first line of `adb devices` just says "List of attached devices", so
46 # skip that.
47 devices = []
48 for line in out[1:]:
49 if not line.strip():
50 continue
51 if 'offline' in line:
52 continue
53
54 serial, _ = re.split(r'\s+', line, maxsplit=1)
55 devices.append(serial)
56 return devices
57
58
59def _get_unique_device(product=None):
60 devices = get_devices()
61 if len(devices) != 1:
62 raise NoUniqueDeviceError()
63 return AndroidDevice(devices[0], product)
64
Dan Alberte2b4a5f2015-07-28 14:53:03 -070065
Dan Albert8e1fdd72015-07-24 17:08:33 -070066def _get_device_by_serial(serial, product=None):
67 for device in get_devices():
68 if device == serial:
69 return AndroidDevice(serial, product)
70 raise DeviceNotFoundError(serial)
71
72
73def get_device(serial=None, product=None):
74 """Get a uniquely identified AndroidDevice if one is available.
75
76 Raises:
77 DeviceNotFoundError:
78 The serial specified by `serial` or $ANDROID_SERIAL is not
79 connected.
80
81 NoUniqueDeviceError:
82 Neither `serial` nor $ANDROID_SERIAL was set, and the number of
83 devices connected to the system is not 1. Having 0 connected
84 devices will also result in this error.
85
86 Returns:
87 An AndroidDevice associated with the first non-None identifier in the
88 following order of preference:
89
90 1) The `serial` argument.
91 2) The environment variable $ANDROID_SERIAL.
92 3) The single device connnected to the system.
93 """
94 if serial is not None:
95 return _get_device_by_serial(serial, product)
96
97 android_serial = os.getenv('ANDROID_SERIAL')
98 if android_serial is not None:
99 return _get_device_by_serial(android_serial, product)
100
101 return _get_unique_device(product)
102
Spencer Lowde4505f2015-08-27 20:58:29 -0700103# Call this instead of subprocess.check_output() to work-around issue in Python
104# 2's subprocess class on Windows where it doesn't support Unicode. This
105# writes the command line to a UTF-8 batch file that is properly interpreted
106# by cmd.exe.
107def _subprocess_check_output(*popenargs, **kwargs):
108 # Only do this slow work-around if Unicode is in the cmd line.
109 if (os.name == 'nt' and
110 any(isinstance(arg, unicode) for arg in popenargs[0])):
111 # cmd.exe requires a suffix to know that it is running a batch file
112 tf = tempfile.NamedTemporaryFile('wb', suffix='.cmd', delete=False)
113 # @ in batch suppresses echo of the current line.
114 # Change the codepage to 65001, the UTF-8 codepage.
115 tf.write('@chcp 65001 > nul\r\n')
116 tf.write('@')
117 # Properly quote all the arguments and encode in UTF-8.
118 tf.write(subprocess.list2cmdline(popenargs[0]).encode('utf-8'))
119 tf.close()
120
121 try:
122 result = subprocess.check_output(['cmd.exe', '/c', tf.name],
123 **kwargs)
124 except subprocess.CalledProcessError as e:
125 # Show real command line instead of the cmd.exe command line.
126 raise subprocess.CalledProcessError(e.returncode, popenargs[0],
127 output=e.output)
128 finally:
129 os.remove(tf.name)
130 return result
131 else:
132 return subprocess.check_output(*popenargs, **kwargs)
Dan Albert8e1fdd72015-07-24 17:08:33 -0700133
134class AndroidDevice(object):
David Purselld4093f12015-08-10 12:52:16 -0700135 # Delimiter string to indicate the start of the exit code.
136 _RETURN_CODE_DELIMITER = 'x'
137
138 # Follow any shell command with this string to get the exit
139 # status of a program since this isn't propagated by adb.
140 #
141 # The delimiter is needed because `printf 1; echo $?` would print
142 # "10", and we wouldn't be able to distinguish the exit code.
143 _RETURN_CODE_PROBE_STRING = 'echo "{0}$?"'.format(_RETURN_CODE_DELIMITER)
144
145 # Maximum search distance from the output end to find the delimiter.
Spencer Lowb7e79af2015-08-16 16:38:47 -0700146 # adb on Windows returns \r\n even if adbd returns \n.
147 _RETURN_CODE_SEARCH_LENGTH = len('{0}255\r\n'.format(_RETURN_CODE_DELIMITER))
David Purselld4093f12015-08-10 12:52:16 -0700148
Dan Albert8e1fdd72015-07-24 17:08:33 -0700149 def __init__(self, serial, product=None):
150 self.serial = serial
151 self.product = product
152 self.adb_cmd = ['adb']
153 if self.serial is not None:
154 self.adb_cmd.extend(['-s', serial])
155 if self.product is not None:
156 self.adb_cmd.extend(['-p', product])
157 self._linesep = None
Dan Albert8e1fdd72015-07-24 17:08:33 -0700158
159 @property
160 def linesep(self):
161 if self._linesep is None:
David Purselld4093f12015-08-10 12:52:16 -0700162 self._linesep = subprocess.check_output(self.adb_cmd +
163 ['shell', 'echo'])
Dan Albert8e1fdd72015-07-24 17:08:33 -0700164 return self._linesep
165
166 def _make_shell_cmd(self, user_cmd):
David Purselld4093f12015-08-10 12:52:16 -0700167 return (self.adb_cmd + ['shell'] + user_cmd +
168 ['; ' + self._RETURN_CODE_PROBE_STRING])
Dan Albert8e1fdd72015-07-24 17:08:33 -0700169
David Purselld4093f12015-08-10 12:52:16 -0700170 def _parse_shell_output(self, out):
171 """Finds the exit code string from shell output.
172
173 Args:
174 out: Shell output string.
175
176 Returns:
177 An (exit_code, output_string) tuple. The output string is
178 cleaned of any additional stuff we appended to find the
179 exit code.
180
181 Raises:
182 RuntimeError: Could not find the exit code in |out|.
183 """
Dan Albert8e1fdd72015-07-24 17:08:33 -0700184 search_text = out
David Purselld4093f12015-08-10 12:52:16 -0700185 if len(search_text) > self._RETURN_CODE_SEARCH_LENGTH:
186 # We don't want to search over massive amounts of data when we know
187 # the part we want is right at the end.
188 search_text = search_text[-self._RETURN_CODE_SEARCH_LENGTH:]
189 partition = search_text.rpartition(self._RETURN_CODE_DELIMITER)
190 if partition[1] == '':
Dan Albert8e1fdd72015-07-24 17:08:33 -0700191 raise RuntimeError('Could not find exit status in shell output.')
David Purselld4093f12015-08-10 12:52:16 -0700192 result = int(partition[2])
193 # partition[0] won't contain the full text if search_text was truncated,
194 # pull from the original string instead.
195 out = out[:-len(partition[1]) - len(partition[2])]
Dan Albert8e1fdd72015-07-24 17:08:33 -0700196 return result, out
197
198 def _simple_call(self, cmd):
Yasuhiro Matsudaab379832015-07-03 02:08:55 +0900199 logging.info(' '.join(self.adb_cmd + cmd))
Spencer Lowde4505f2015-08-27 20:58:29 -0700200 return _subprocess_check_output(
Dan Albert8e1fdd72015-07-24 17:08:33 -0700201 self.adb_cmd + cmd, stderr=subprocess.STDOUT)
202
203 def shell(self, cmd):
Yasuhiro Matsudaab379832015-07-03 02:08:55 +0900204 logging.info(' '.join(self.adb_cmd + ['shell'] + cmd))
Dan Albert8e1fdd72015-07-24 17:08:33 -0700205 cmd = self._make_shell_cmd(cmd)
Spencer Lowde4505f2015-08-27 20:58:29 -0700206 out = _subprocess_check_output(cmd)
Dan Albert8e1fdd72015-07-24 17:08:33 -0700207 rc, out = self._parse_shell_output(out)
208 if rc != 0:
209 error = subprocess.CalledProcessError(rc, cmd)
210 error.out = out
211 raise error
212 return out
213
214 def shell_nocheck(self, cmd):
215 cmd = self._make_shell_cmd(cmd)
Yasuhiro Matsudaab379832015-07-03 02:08:55 +0900216 logging.info(' '.join(cmd))
Dan Albert8e1fdd72015-07-24 17:08:33 -0700217 p = subprocess.Popen(
218 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
219 out, _ = p.communicate()
220 return self._parse_shell_output(out)
221
Yasuhiro Matsudac0822e82015-08-05 19:49:03 +0900222 def install(self, filename, replace=False):
223 cmd = ['install']
224 if replace:
225 cmd.append('-r')
226 cmd.append(filename)
227 return self._simple_call(cmd)
Dan Albert8e1fdd72015-07-24 17:08:33 -0700228
229 def push(self, local, remote):
230 return self._simple_call(['push', local, remote])
231
232 def pull(self, remote, local):
233 return self._simple_call(['pull', remote, local])
234
235 def sync(self, directory=None):
236 cmd = ['sync']
237 if directory is not None:
238 cmd.append(directory)
239 return self._simple_call(cmd)
240
241 def forward(self, local, remote):
242 return self._simple_call(['forward', local, remote])
243
244 def tcpip(self, port):
245 return self._simple_call(['tcpip', port])
246
247 def usb(self):
248 return self._simple_call(['usb'])
249
Yasuhiro Matsudaab379832015-07-03 02:08:55 +0900250 def reboot(self):
251 return self._simple_call(['reboot'])
252
Dan Albert8e1fdd72015-07-24 17:08:33 -0700253 def root(self):
254 return self._simple_call(['root'])
255
256 def unroot(self):
257 return self._simple_call(['unroot'])
258
259 def forward_remove(self, local):
260 return self._simple_call(['forward', '--remove', local])
261
262 def forward_remove_all(self):
263 return self._simple_call(['forward', '--remove-all'])
264
265 def connect(self, host):
266 return self._simple_call(['connect', host])
267
268 def disconnect(self, host):
269 return self._simple_call(['disconnect', host])
270
271 def reverse(self, remote, local):
272 return self._simple_call(['reverse', remote, local])
273
274 def reverse_remove_all(self):
275 return self._simple_call(['reverse', '--remove-all'])
276
277 def reverse_remove(self, remote):
278 return self._simple_call(['reverse', '--remove', remote])
279
280 def wait(self):
281 return self._simple_call(['wait-for-device'])
282
283 def get_prop(self, prop_name):
Dan Alberte2b4a5f2015-07-28 14:53:03 -0700284 output = self.shell(['getprop', prop_name]).splitlines()
Dan Albert8e1fdd72015-07-24 17:08:33 -0700285 if len(output) != 1:
286 raise RuntimeError('Too many lines in getprop output:\n' +
287 '\n'.join(output))
288 value = output[0]
289 if not value.strip():
290 return None
291 return value
292
293 def set_prop(self, prop_name, value):
294 self.shell(['setprop', prop_name, value])