blob: 5b33ff2feae0a0941f72096224e6d99af82ce7f6 [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
20
21
22class FindDeviceError(RuntimeError):
23 pass
24
25
26class DeviceNotFoundError(FindDeviceError):
27 def __init__(self, serial):
28 self.serial = serial
29 super(DeviceNotFoundError, self).__init__(
30 'No device with serial {}'.format(serial))
31
32
33class NoUniqueDeviceError(FindDeviceError):
34 def __init__(self):
35 super(NoUniqueDeviceError, self).__init__('No unique device')
36
37
38def get_devices():
39 with open(os.devnull, 'wb') as devnull:
40 subprocess.check_call(['adb', 'start-server'], stdout=devnull,
41 stderr=devnull)
42 out = subprocess.check_output(['adb', 'devices']).splitlines()
43
44 # The first line of `adb devices` just says "List of attached devices", so
45 # skip that.
46 devices = []
47 for line in out[1:]:
48 if not line.strip():
49 continue
50 if 'offline' in line:
51 continue
52
53 serial, _ = re.split(r'\s+', line, maxsplit=1)
54 devices.append(serial)
55 return devices
56
57
58def _get_unique_device(product=None):
59 devices = get_devices()
60 if len(devices) != 1:
61 raise NoUniqueDeviceError()
62 return AndroidDevice(devices[0], product)
63
Dan Alberte2b4a5f2015-07-28 14:53:03 -070064
Dan Albert8e1fdd72015-07-24 17:08:33 -070065def _get_device_by_serial(serial, product=None):
66 for device in get_devices():
67 if device == serial:
68 return AndroidDevice(serial, product)
69 raise DeviceNotFoundError(serial)
70
71
72def get_device(serial=None, product=None):
73 """Get a uniquely identified AndroidDevice if one is available.
74
75 Raises:
76 DeviceNotFoundError:
77 The serial specified by `serial` or $ANDROID_SERIAL is not
78 connected.
79
80 NoUniqueDeviceError:
81 Neither `serial` nor $ANDROID_SERIAL was set, and the number of
82 devices connected to the system is not 1. Having 0 connected
83 devices will also result in this error.
84
85 Returns:
86 An AndroidDevice associated with the first non-None identifier in the
87 following order of preference:
88
89 1) The `serial` argument.
90 2) The environment variable $ANDROID_SERIAL.
91 3) The single device connnected to the system.
92 """
93 if serial is not None:
94 return _get_device_by_serial(serial, product)
95
96 android_serial = os.getenv('ANDROID_SERIAL')
97 if android_serial is not None:
98 return _get_device_by_serial(android_serial, product)
99
100 return _get_unique_device(product)
101
102
103class AndroidDevice(object):
David Purselld4093f12015-08-10 12:52:16 -0700104 # Delimiter string to indicate the start of the exit code.
105 _RETURN_CODE_DELIMITER = 'x'
106
107 # Follow any shell command with this string to get the exit
108 # status of a program since this isn't propagated by adb.
109 #
110 # The delimiter is needed because `printf 1; echo $?` would print
111 # "10", and we wouldn't be able to distinguish the exit code.
112 _RETURN_CODE_PROBE_STRING = 'echo "{0}$?"'.format(_RETURN_CODE_DELIMITER)
113
114 # Maximum search distance from the output end to find the delimiter.
Spencer Lowb7e79af2015-08-16 16:38:47 -0700115 # adb on Windows returns \r\n even if adbd returns \n.
116 _RETURN_CODE_SEARCH_LENGTH = len('{0}255\r\n'.format(_RETURN_CODE_DELIMITER))
David Purselld4093f12015-08-10 12:52:16 -0700117
Dan Albert8e1fdd72015-07-24 17:08:33 -0700118 def __init__(self, serial, product=None):
119 self.serial = serial
120 self.product = product
121 self.adb_cmd = ['adb']
122 if self.serial is not None:
123 self.adb_cmd.extend(['-s', serial])
124 if self.product is not None:
125 self.adb_cmd.extend(['-p', product])
126 self._linesep = None
Dan Albert8e1fdd72015-07-24 17:08:33 -0700127
128 @property
129 def linesep(self):
130 if self._linesep is None:
David Purselld4093f12015-08-10 12:52:16 -0700131 self._linesep = subprocess.check_output(self.adb_cmd +
132 ['shell', 'echo'])
Dan Albert8e1fdd72015-07-24 17:08:33 -0700133 return self._linesep
134
135 def _make_shell_cmd(self, user_cmd):
David Purselld4093f12015-08-10 12:52:16 -0700136 return (self.adb_cmd + ['shell'] + user_cmd +
137 ['; ' + self._RETURN_CODE_PROBE_STRING])
Dan Albert8e1fdd72015-07-24 17:08:33 -0700138
David Purselld4093f12015-08-10 12:52:16 -0700139 def _parse_shell_output(self, out):
140 """Finds the exit code string from shell output.
141
142 Args:
143 out: Shell output string.
144
145 Returns:
146 An (exit_code, output_string) tuple. The output string is
147 cleaned of any additional stuff we appended to find the
148 exit code.
149
150 Raises:
151 RuntimeError: Could not find the exit code in |out|.
152 """
Dan Albert8e1fdd72015-07-24 17:08:33 -0700153 search_text = out
David Purselld4093f12015-08-10 12:52:16 -0700154 if len(search_text) > self._RETURN_CODE_SEARCH_LENGTH:
155 # We don't want to search over massive amounts of data when we know
156 # the part we want is right at the end.
157 search_text = search_text[-self._RETURN_CODE_SEARCH_LENGTH:]
158 partition = search_text.rpartition(self._RETURN_CODE_DELIMITER)
159 if partition[1] == '':
Dan Albert8e1fdd72015-07-24 17:08:33 -0700160 raise RuntimeError('Could not find exit status in shell output.')
David Purselld4093f12015-08-10 12:52:16 -0700161 result = int(partition[2])
162 # partition[0] won't contain the full text if search_text was truncated,
163 # pull from the original string instead.
164 out = out[:-len(partition[1]) - len(partition[2])]
Dan Albert8e1fdd72015-07-24 17:08:33 -0700165 return result, out
166
167 def _simple_call(self, cmd):
Yasuhiro Matsudaab379832015-07-03 02:08:55 +0900168 logging.info(' '.join(self.adb_cmd + cmd))
Dan Albert8e1fdd72015-07-24 17:08:33 -0700169 return subprocess.check_output(
170 self.adb_cmd + cmd, stderr=subprocess.STDOUT)
171
172 def shell(self, cmd):
Yasuhiro Matsudaab379832015-07-03 02:08:55 +0900173 logging.info(' '.join(self.adb_cmd + ['shell'] + cmd))
Dan Albert8e1fdd72015-07-24 17:08:33 -0700174 cmd = self._make_shell_cmd(cmd)
175 out = subprocess.check_output(cmd)
176 rc, out = self._parse_shell_output(out)
177 if rc != 0:
178 error = subprocess.CalledProcessError(rc, cmd)
179 error.out = out
180 raise error
181 return out
182
183 def shell_nocheck(self, cmd):
184 cmd = self._make_shell_cmd(cmd)
Yasuhiro Matsudaab379832015-07-03 02:08:55 +0900185 logging.info(' '.join(cmd))
Dan Albert8e1fdd72015-07-24 17:08:33 -0700186 p = subprocess.Popen(
187 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
188 out, _ = p.communicate()
189 return self._parse_shell_output(out)
190
Yasuhiro Matsudac0822e82015-08-05 19:49:03 +0900191 def install(self, filename, replace=False):
192 cmd = ['install']
193 if replace:
194 cmd.append('-r')
195 cmd.append(filename)
196 return self._simple_call(cmd)
Dan Albert8e1fdd72015-07-24 17:08:33 -0700197
198 def push(self, local, remote):
199 return self._simple_call(['push', local, remote])
200
201 def pull(self, remote, local):
202 return self._simple_call(['pull', remote, local])
203
204 def sync(self, directory=None):
205 cmd = ['sync']
206 if directory is not None:
207 cmd.append(directory)
208 return self._simple_call(cmd)
209
210 def forward(self, local, remote):
211 return self._simple_call(['forward', local, remote])
212
213 def tcpip(self, port):
214 return self._simple_call(['tcpip', port])
215
216 def usb(self):
217 return self._simple_call(['usb'])
218
Yasuhiro Matsudaab379832015-07-03 02:08:55 +0900219 def reboot(self):
220 return self._simple_call(['reboot'])
221
Dan Albert8e1fdd72015-07-24 17:08:33 -0700222 def root(self):
223 return self._simple_call(['root'])
224
225 def unroot(self):
226 return self._simple_call(['unroot'])
227
228 def forward_remove(self, local):
229 return self._simple_call(['forward', '--remove', local])
230
231 def forward_remove_all(self):
232 return self._simple_call(['forward', '--remove-all'])
233
234 def connect(self, host):
235 return self._simple_call(['connect', host])
236
237 def disconnect(self, host):
238 return self._simple_call(['disconnect', host])
239
240 def reverse(self, remote, local):
241 return self._simple_call(['reverse', remote, local])
242
243 def reverse_remove_all(self):
244 return self._simple_call(['reverse', '--remove-all'])
245
246 def reverse_remove(self, remote):
247 return self._simple_call(['reverse', '--remove', remote])
248
249 def wait(self):
250 return self._simple_call(['wait-for-device'])
251
252 def get_prop(self, prop_name):
Dan Alberte2b4a5f2015-07-28 14:53:03 -0700253 output = self.shell(['getprop', prop_name]).splitlines()
Dan Albert8e1fdd72015-07-24 17:08:33 -0700254 if len(output) != 1:
255 raise RuntimeError('Too many lines in getprop output:\n' +
256 '\n'.join(output))
257 value = output[0]
258 if not value.strip():
259 return None
260 return value
261
262 def set_prop(self, prop_name, value):
263 self.shell(['setprop', prop_name, value])