blob: e0da8f5a02c92f43fdda8dbbb6a82bdb88453c99 [file] [log] [blame]
Dan Albert32e13072015-09-18 13:37:17 -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#
Josh Gao21f98492015-09-21 11:49:23 -070016import atexit
Spencer Low82aa7da2015-11-17 19:35:22 -080017import base64
Dan Albert32e13072015-09-18 13:37:17 -070018import logging
19import os
20import re
21import subprocess
Dan Albert32e13072015-09-18 13:37:17 -070022
23
24class FindDeviceError(RuntimeError):
25 pass
26
27
28class DeviceNotFoundError(FindDeviceError):
29 def __init__(self, serial):
30 self.serial = serial
31 super(DeviceNotFoundError, self).__init__(
32 'No device with serial {}'.format(serial))
33
34
35class NoUniqueDeviceError(FindDeviceError):
36 def __init__(self):
37 super(NoUniqueDeviceError, self).__init__('No unique device')
38
39
40class ShellError(RuntimeError):
41 def __init__(self, cmd, stdout, stderr, exit_code):
42 super(ShellError, self).__init__(
Dan Albert5ca14aa2016-02-24 11:38:40 -080043 '`{0}` exited with code {1}'.format(cmd, exit_code))
Dan Albert32e13072015-09-18 13:37:17 -070044 self.cmd = cmd
45 self.stdout = stdout
46 self.stderr = stderr
47 self.exit_code = exit_code
48
49
Josh Gao3fd43b52015-10-28 11:56:13 -070050def get_devices(adb_path='adb'):
Dan Albert32e13072015-09-18 13:37:17 -070051 with open(os.devnull, 'wb') as devnull:
Josh Gao3fd43b52015-10-28 11:56:13 -070052 subprocess.check_call([adb_path, 'start-server'], stdout=devnull,
Dan Albert32e13072015-09-18 13:37:17 -070053 stderr=devnull)
Dan Albert1b06c6e2018-02-14 12:46:31 -080054 out = split_lines(
55 subprocess.check_output([adb_path, 'devices']).decode('utf-8'))
Dan Albert32e13072015-09-18 13:37:17 -070056
57 # The first line of `adb devices` just says "List of attached devices", so
58 # skip that.
59 devices = []
60 for line in out[1:]:
61 if not line.strip():
62 continue
63 if 'offline' in line:
64 continue
65
66 serial, _ = re.split(r'\s+', line, maxsplit=1)
67 devices.append(serial)
68 return devices
69
70
Josh Gao3fd43b52015-10-28 11:56:13 -070071def _get_unique_device(product=None, adb_path='adb'):
72 devices = get_devices(adb_path=adb_path)
Dan Albert32e13072015-09-18 13:37:17 -070073 if len(devices) != 1:
74 raise NoUniqueDeviceError()
Josh Gao3fd43b52015-10-28 11:56:13 -070075 return AndroidDevice(devices[0], product, adb_path)
Dan Albert32e13072015-09-18 13:37:17 -070076
77
Josh Gao3fd43b52015-10-28 11:56:13 -070078def _get_device_by_serial(serial, product=None, adb_path='adb'):
79 for device in get_devices(adb_path=adb_path):
Dan Albert32e13072015-09-18 13:37:17 -070080 if device == serial:
Josh Gao3fd43b52015-10-28 11:56:13 -070081 return AndroidDevice(serial, product, adb_path)
Dan Albert32e13072015-09-18 13:37:17 -070082 raise DeviceNotFoundError(serial)
83
84
Josh Gao3fd43b52015-10-28 11:56:13 -070085def get_device(serial=None, product=None, adb_path='adb'):
Dan Albert32e13072015-09-18 13:37:17 -070086 """Get a uniquely identified AndroidDevice if one is available.
87
88 Raises:
89 DeviceNotFoundError:
90 The serial specified by `serial` or $ANDROID_SERIAL is not
91 connected.
92
93 NoUniqueDeviceError:
94 Neither `serial` nor $ANDROID_SERIAL was set, and the number of
95 devices connected to the system is not 1. Having 0 connected
96 devices will also result in this error.
97
98 Returns:
99 An AndroidDevice associated with the first non-None identifier in the
100 following order of preference:
101
102 1) The `serial` argument.
103 2) The environment variable $ANDROID_SERIAL.
104 3) The single device connnected to the system.
105 """
106 if serial is not None:
Josh Gao3fd43b52015-10-28 11:56:13 -0700107 return _get_device_by_serial(serial, product, adb_path)
Dan Albert32e13072015-09-18 13:37:17 -0700108
109 android_serial = os.getenv('ANDROID_SERIAL')
110 if android_serial is not None:
Josh Gao3fd43b52015-10-28 11:56:13 -0700111 return _get_device_by_serial(android_serial, product, adb_path)
Dan Albert32e13072015-09-18 13:37:17 -0700112
Josh Gao3fd43b52015-10-28 11:56:13 -0700113 return _get_unique_device(product, adb_path=adb_path)
Dan Albert32e13072015-09-18 13:37:17 -0700114
Josh Gaoa8731c42015-09-21 11:47:52 -0700115
Josh Gao3fd43b52015-10-28 11:56:13 -0700116def _get_device_by_type(flag, adb_path):
Josh Gaoa8731c42015-09-21 11:47:52 -0700117 with open(os.devnull, 'wb') as devnull:
Josh Gao3fd43b52015-10-28 11:56:13 -0700118 subprocess.check_call([adb_path, 'start-server'], stdout=devnull,
Josh Gaoa8731c42015-09-21 11:47:52 -0700119 stderr=devnull)
120 try:
Dan Albert5ca14aa2016-02-24 11:38:40 -0800121 serial = subprocess.check_output(
Dan Albert1b06c6e2018-02-14 12:46:31 -0800122 [adb_path, flag, 'get-serialno']).decode('utf-8').strip()
Josh Gaoa8731c42015-09-21 11:47:52 -0700123 except subprocess.CalledProcessError:
124 raise RuntimeError('adb unexpectedly returned nonzero')
125 if serial == 'unknown':
126 raise NoUniqueDeviceError()
Josh Gao3fd43b52015-10-28 11:56:13 -0700127 return _get_device_by_serial(serial, adb_path=adb_path)
Josh Gaoa8731c42015-09-21 11:47:52 -0700128
129
Josh Gao3fd43b52015-10-28 11:56:13 -0700130def get_usb_device(adb_path='adb'):
Josh Gaoa8731c42015-09-21 11:47:52 -0700131 """Get the unique USB-connected AndroidDevice if it is available.
132
133 Raises:
134 NoUniqueDeviceError:
135 0 or multiple devices are connected via USB.
136
137 Returns:
138 An AndroidDevice associated with the unique USB-connected device.
139 """
Josh Gao3fd43b52015-10-28 11:56:13 -0700140 return _get_device_by_type('-d', adb_path=adb_path)
Josh Gaoa8731c42015-09-21 11:47:52 -0700141
142
Josh Gao3fd43b52015-10-28 11:56:13 -0700143def get_emulator_device(adb_path='adb'):
Josh Gaoa8731c42015-09-21 11:47:52 -0700144 """Get the unique emulator AndroidDevice if it is available.
145
146 Raises:
147 NoUniqueDeviceError:
148 0 or multiple emulators are running.
149
150 Returns:
151 An AndroidDevice associated with the unique running emulator.
152 """
Josh Gao3fd43b52015-10-28 11:56:13 -0700153 return _get_device_by_type('-e', adb_path=adb_path)
Josh Gaoa8731c42015-09-21 11:47:52 -0700154
155
Dan Albert5ca14aa2016-02-24 11:38:40 -0800156# If necessary, modifies subprocess.check_output() or subprocess.Popen() args
157# to run the subprocess via Windows PowerShell to work-around an issue in
158# Python 2's subprocess class on Windows where it doesn't support Unicode.
Spencer Low82aa7da2015-11-17 19:35:22 -0800159def _get_subprocess_args(args):
Dan Albert5ca14aa2016-02-24 11:38:40 -0800160 # Only do this slow work-around if Unicode is in the cmd line on Windows.
161 # PowerShell takes 600-700ms to startup on a 2013-2014 machine, which is
162 # very slow.
163 if os.name != 'nt' or all(not isinstance(arg, unicode) for arg in args[0]):
Spencer Low41609582015-09-21 14:15:15 -0700164 return args
165
Spencer Low82aa7da2015-11-17 19:35:22 -0800166 def escape_arg(arg):
Dan Albert5ca14aa2016-02-24 11:38:40 -0800167 # Escape for the parsing that the C Runtime does in Windows apps. In
168 # particular, this will take care of double-quotes.
Spencer Low82aa7da2015-11-17 19:35:22 -0800169 arg = subprocess.list2cmdline([arg])
Dan Albert5ca14aa2016-02-24 11:38:40 -0800170 # Escape single-quote with another single-quote because we're about
171 # to...
Spencer Low82aa7da2015-11-17 19:35:22 -0800172 arg = arg.replace(u"'", u"''")
173 # ...put the arg in a single-quoted string for PowerShell to parse.
174 arg = u"'" + arg + u"'"
175 return arg
176
177 # Escape command line args.
178 argv = map(escape_arg, args[0])
Dan Albert5ca14aa2016-02-24 11:38:40 -0800179 # Cause script errors (such as adb not found) to stop script immediately
180 # with an error.
181 ps_code = u'$ErrorActionPreference = "Stop"\r\n'
182 # Add current directory to the PATH var, to match cmd.exe/CreateProcess()
183 # behavior.
184 ps_code += u'$env:Path = ".;" + $env:Path\r\n'
Spencer Low82aa7da2015-11-17 19:35:22 -0800185 # Precede by &, the PowerShell call operator, and separate args by space.
186 ps_code += u'& ' + u' '.join(argv)
187 # Make the PowerShell exit code the exit code of the subprocess.
188 ps_code += u'\r\nExit $LastExitCode'
Dan Albert5ca14aa2016-02-24 11:38:40 -0800189 # Encode as UTF-16LE (without Byte-Order-Mark) which Windows natively
190 # understands.
Spencer Low82aa7da2015-11-17 19:35:22 -0800191 ps_code = ps_code.encode('utf-16le')
192
Dan Albert5ca14aa2016-02-24 11:38:40 -0800193 # Encode the PowerShell command as base64 and use the special
194 # -EncodedCommand option that base64 decodes. Base64 is just plain ASCII,
195 # so it should have no problem passing through Win32 CreateProcessA()
196 # (which python erroneously calls instead of CreateProcessW()).
197 return (['powershell.exe', '-NoProfile', '-NonInteractive',
198 '-EncodedCommand', base64.b64encode(ps_code)],) + args[1:]
Spencer Low82aa7da2015-11-17 19:35:22 -0800199
Spencer Low41609582015-09-21 14:15:15 -0700200
Dan Albert32e13072015-09-18 13:37:17 -0700201# Call this instead of subprocess.check_output() to work-around issue in Python
Spencer Low82aa7da2015-11-17 19:35:22 -0800202# 2's subprocess class on Windows where it doesn't support Unicode.
Spencer Low41609582015-09-21 14:15:15 -0700203def _subprocess_check_output(*args, **kwargs):
Spencer Low82aa7da2015-11-17 19:35:22 -0800204 try:
205 return subprocess.check_output(*_get_subprocess_args(args), **kwargs)
206 except subprocess.CalledProcessError as e:
207 # Show real command line instead of the powershell.exe command line.
208 raise subprocess.CalledProcessError(e.returncode, args[0],
209 output=e.output)
Spencer Low41609582015-09-21 14:15:15 -0700210
211
212# Call this instead of subprocess.Popen(). Like _subprocess_check_output().
Spencer Low82aa7da2015-11-17 19:35:22 -0800213def _subprocess_Popen(*args, **kwargs):
214 return subprocess.Popen(*_get_subprocess_args(args), **kwargs)
Spencer Low41609582015-09-21 14:15:15 -0700215
Dan Albert32e13072015-09-18 13:37:17 -0700216
Josh Gao7f6e8dd2016-03-28 11:24:42 -0700217def split_lines(s):
Dan Albert9bee8c22016-02-24 12:28:45 -0800218 """Splits lines in a way that works even on Windows and old devices.
219
220 Windows will see \r\n instead of \n, old devices do the same, old devices
221 on Windows will see \r\r\n.
222 """
223 # rstrip is used here to workaround a difference between splineslines and
224 # re.split:
225 # >>> 'foo\n'.splitlines()
226 # ['foo']
227 # >>> re.split(r'\n', 'foo\n')
228 # ['foo', '']
229 return re.split(r'[\r\n]+', s.rstrip())
230
231
Josh Gao016c18c2016-03-29 11:28:36 -0700232def version(adb_path=None):
Josh Gao1d404e62016-03-28 14:10:57 -0700233 """Get the version of adb (in terms of ADB_SERVER_VERSION)."""
234
Josh Gao016c18c2016-03-29 11:28:36 -0700235 adb_path = adb_path if adb_path is not None else ['adb']
236 version_output = subprocess.check_output(adb_path + ['version'])
Dan Albert1b06c6e2018-02-14 12:46:31 -0800237 version_output = version_output.decode('utf-8')
Josh Gao1d404e62016-03-28 14:10:57 -0700238 pattern = r'^Android Debug Bridge version 1.0.(\d+)$'
239 result = re.match(pattern, version_output.splitlines()[0])
240 if not result:
241 return 0
242 return int(result.group(1))
243
244
Dan Albert32e13072015-09-18 13:37:17 -0700245class AndroidDevice(object):
246 # Delimiter string to indicate the start of the exit code.
247 _RETURN_CODE_DELIMITER = 'x'
248
249 # Follow any shell command with this string to get the exit
250 # status of a program since this isn't propagated by adb.
251 #
252 # The delimiter is needed because `printf 1; echo $?` would print
253 # "10", and we wouldn't be able to distinguish the exit code.
David Pursell51f2cc22015-11-05 11:26:42 -0800254 _RETURN_CODE_PROBE = [';', 'echo', '{0}$?'.format(_RETURN_CODE_DELIMITER)]
Dan Albert32e13072015-09-18 13:37:17 -0700255
256 # Maximum search distance from the output end to find the delimiter.
Dan Albertaaf54552016-02-24 11:39:42 -0800257 # adb on Windows returns \r\n even if adbd returns \n. Some old devices
258 # seem to actually return \r\r\n.
Dan Albert5ca14aa2016-02-24 11:38:40 -0800259 _RETURN_CODE_SEARCH_LENGTH = len(
Dan Albertaaf54552016-02-24 11:39:42 -0800260 '{0}255\r\r\n'.format(_RETURN_CODE_DELIMITER))
Dan Albert32e13072015-09-18 13:37:17 -0700261
Josh Gao3fd43b52015-10-28 11:56:13 -0700262 def __init__(self, serial, product=None, adb_path='adb'):
Dan Albert32e13072015-09-18 13:37:17 -0700263 self.serial = serial
264 self.product = product
Ryan Prichard5b1219e2017-10-19 16:43:52 -0700265 self.adb_path = adb_path
Josh Gao3fd43b52015-10-28 11:56:13 -0700266 self.adb_cmd = [adb_path]
267
Dan Albert32e13072015-09-18 13:37:17 -0700268 if self.serial is not None:
269 self.adb_cmd.extend(['-s', serial])
270 if self.product is not None:
271 self.adb_cmd.extend(['-p', product])
272 self._linesep = None
273 self._features = None
274
275 @property
276 def linesep(self):
277 if self._linesep is None:
Dan Albert1b06c6e2018-02-14 12:46:31 -0800278 self._linesep = subprocess.check_output(
279 self.adb_cmd + ['shell', 'echo']).decode('utf-8')
Dan Albert32e13072015-09-18 13:37:17 -0700280 return self._linesep
281
282 @property
283 def features(self):
284 if self._features is None:
285 try:
Josh Gao7f6e8dd2016-03-28 11:24:42 -0700286 self._features = split_lines(self._simple_call(['features']))
Dan Albert32e13072015-09-18 13:37:17 -0700287 except subprocess.CalledProcessError:
288 self._features = []
289 return self._features
290
Josh Gao1d404e62016-03-28 14:10:57 -0700291 def has_shell_protocol(self):
292 return version(self.adb_cmd) >= 35 and 'shell_v2' in self.features
293
Dan Albert32e13072015-09-18 13:37:17 -0700294 def _make_shell_cmd(self, user_cmd):
295 command = self.adb_cmd + ['shell'] + user_cmd
Josh Gao016c18c2016-03-29 11:28:36 -0700296 if not self.has_shell_protocol():
David Pursell51f2cc22015-11-05 11:26:42 -0800297 command += self._RETURN_CODE_PROBE
Dan Albert32e13072015-09-18 13:37:17 -0700298 return command
299
300 def _parse_shell_output(self, out):
301 """Finds the exit code string from shell output.
302
303 Args:
304 out: Shell output string.
305
306 Returns:
307 An (exit_code, output_string) tuple. The output string is
308 cleaned of any additional stuff we appended to find the
309 exit code.
310
311 Raises:
312 RuntimeError: Could not find the exit code in |out|.
313 """
314 search_text = out
315 if len(search_text) > self._RETURN_CODE_SEARCH_LENGTH:
316 # We don't want to search over massive amounts of data when we know
317 # the part we want is right at the end.
318 search_text = search_text[-self._RETURN_CODE_SEARCH_LENGTH:]
319 partition = search_text.rpartition(self._RETURN_CODE_DELIMITER)
320 if partition[1] == '':
321 raise RuntimeError('Could not find exit status in shell output.')
322 result = int(partition[2])
Dan Albert5ca14aa2016-02-24 11:38:40 -0800323 # partition[0] won't contain the full text if search_text was
324 # truncated, pull from the original string instead.
Dan Albert32e13072015-09-18 13:37:17 -0700325 out = out[:-len(partition[1]) - len(partition[2])]
326 return result, out
327
328 def _simple_call(self, cmd):
329 logging.info(' '.join(self.adb_cmd + cmd))
330 return _subprocess_check_output(
Dan Albert1b06c6e2018-02-14 12:46:31 -0800331 self.adb_cmd + cmd, stderr=subprocess.STDOUT).decode('utf-8')
Dan Albert32e13072015-09-18 13:37:17 -0700332
333 def shell(self, cmd):
334 """Calls `adb shell`
335
336 Args:
David Pursell45d61d02015-10-12 15:54:58 -0700337 cmd: command to execute as a list of strings.
Dan Albert32e13072015-09-18 13:37:17 -0700338
339 Returns:
340 A (stdout, stderr) tuple. Stderr may be combined into stdout
341 if the device doesn't support separate streams.
342
343 Raises:
344 ShellError: the exit code was non-zero.
345 """
346 exit_code, stdout, stderr = self.shell_nocheck(cmd)
347 if exit_code != 0:
348 raise ShellError(cmd, stdout, stderr, exit_code)
349 return stdout, stderr
350
351 def shell_nocheck(self, cmd):
352 """Calls `adb shell`
353
354 Args:
David Pursell45d61d02015-10-12 15:54:58 -0700355 cmd: command to execute as a list of strings.
Dan Albert32e13072015-09-18 13:37:17 -0700356
357 Returns:
358 An (exit_code, stdout, stderr) tuple. Stderr may be combined
359 into stdout if the device doesn't support separate streams.
360 """
361 cmd = self._make_shell_cmd(cmd)
362 logging.info(' '.join(cmd))
Spencer Low41609582015-09-21 14:15:15 -0700363 p = _subprocess_Popen(
Dan Albert32e13072015-09-18 13:37:17 -0700364 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
365 stdout, stderr = p.communicate()
Dan Albert1b06c6e2018-02-14 12:46:31 -0800366 stdout = stdout.decode('utf-8')
367 stderr = stderr.decode('utf-8')
Josh Gao1d404e62016-03-28 14:10:57 -0700368 if self.has_shell_protocol():
Dan Albert32e13072015-09-18 13:37:17 -0700369 exit_code = p.returncode
370 else:
371 exit_code, stdout = self._parse_shell_output(stdout)
372 return exit_code, stdout, stderr
373
Josh Gao21f98492015-09-21 11:49:23 -0700374 def shell_popen(self, cmd, kill_atexit=True, preexec_fn=None,
375 creationflags=0, **kwargs):
376 """Calls `adb shell` and returns a handle to the adb process.
377
378 This function provides direct access to the subprocess used to run the
379 command, without special return code handling. Users that need the
380 return value must retrieve it themselves.
381
382 Args:
383 cmd: Array of command arguments to execute.
384 kill_atexit: Whether to kill the process upon exiting.
385 preexec_fn: Argument forwarded to subprocess.Popen.
386 creationflags: Argument forwarded to subprocess.Popen.
387 **kwargs: Arguments forwarded to subprocess.Popen.
388
389 Returns:
390 subprocess.Popen handle to the adb shell instance
391 """
392
393 command = self.adb_cmd + ['shell'] + cmd
394
395 # Make sure a ctrl-c in the parent script doesn't kill gdbserver.
396 if os.name == 'nt':
397 creationflags |= subprocess.CREATE_NEW_PROCESS_GROUP
398 else:
399 if preexec_fn is None:
400 preexec_fn = os.setpgrp
401 elif preexec_fn is not os.setpgrp:
402 fn = preexec_fn
403 def _wrapper():
404 fn()
405 os.setpgrp()
406 preexec_fn = _wrapper
407
Spencer Low41609582015-09-21 14:15:15 -0700408 p = _subprocess_Popen(command, creationflags=creationflags,
409 preexec_fn=preexec_fn, **kwargs)
Josh Gao21f98492015-09-21 11:49:23 -0700410
411 if kill_atexit:
412 atexit.register(p.kill)
413
414 return p
415
Dan Albert32e13072015-09-18 13:37:17 -0700416 def install(self, filename, replace=False):
417 cmd = ['install']
418 if replace:
419 cmd.append('-r')
420 cmd.append(filename)
421 return self._simple_call(cmd)
422
Dan Albertc52723d2017-05-19 11:11:43 -0700423 def push(self, local, remote, sync=False):
424 """Transfer a local file or directory to the device.
425
426 Args:
427 local: The local file or directory to transfer.
428 remote: The remote path to which local should be transferred.
429 sync: If True, only transfers files that are newer on the host than
430 those on the device. If False, transfers all files.
431
432 Returns:
433 Exit status of the push command.
434 """
435 cmd = ['push']
436 if sync:
437 cmd.append('--sync')
438 cmd.extend([local, remote])
439 return self._simple_call(cmd)
Dan Albert32e13072015-09-18 13:37:17 -0700440
441 def pull(self, remote, local):
442 return self._simple_call(['pull', remote, local])
443
444 def sync(self, directory=None):
445 cmd = ['sync']
446 if directory is not None:
447 cmd.append(directory)
448 return self._simple_call(cmd)
449
Dan Albert32e13072015-09-18 13:37:17 -0700450 def tcpip(self, port):
451 return self._simple_call(['tcpip', port])
452
453 def usb(self):
454 return self._simple_call(['usb'])
455
456 def reboot(self):
457 return self._simple_call(['reboot'])
458
Josh Gaocac4e972015-09-22 11:31:56 -0700459 def remount(self):
460 return self._simple_call(['remount'])
461
Dan Albert32e13072015-09-18 13:37:17 -0700462 def root(self):
463 return self._simple_call(['root'])
464
465 def unroot(self):
466 return self._simple_call(['unroot'])
467
Dan Albert32e13072015-09-18 13:37:17 -0700468 def connect(self, host):
469 return self._simple_call(['connect', host])
470
471 def disconnect(self, host):
472 return self._simple_call(['disconnect', host])
473
Yabin Cui0e54d102015-10-30 19:33:14 -0700474 def forward(self, local, remote):
475 return self._simple_call(['forward', local, remote])
476
477 def forward_list(self):
478 return self._simple_call(['forward', '--list'])
479
Spencer Low5e50d6b2015-10-14 17:37:22 -0700480 def forward_no_rebind(self, local, remote):
481 return self._simple_call(['forward', '--no-rebind', local, remote])
482
Yabin Cui0e54d102015-10-30 19:33:14 -0700483 def forward_remove(self, local):
484 return self._simple_call(['forward', '--remove', local])
485
486 def forward_remove_all(self):
487 return self._simple_call(['forward', '--remove-all'])
488
Dan Albert32e13072015-09-18 13:37:17 -0700489 def reverse(self, remote, local):
490 return self._simple_call(['reverse', remote, local])
491
Yabin Cui0e54d102015-10-30 19:33:14 -0700492 def reverse_list(self):
493 return self._simple_call(['reverse', '--list'])
494
Spencer Low5e50d6b2015-10-14 17:37:22 -0700495 def reverse_no_rebind(self, local, remote):
496 return self._simple_call(['reverse', '--no-rebind', local, remote])
497
Dan Albert32e13072015-09-18 13:37:17 -0700498 def reverse_remove_all(self):
499 return self._simple_call(['reverse', '--remove-all'])
500
501 def reverse_remove(self, remote):
502 return self._simple_call(['reverse', '--remove', remote])
503
504 def wait(self):
505 return self._simple_call(['wait-for-device'])
506
Dan Albert32e13072015-09-18 13:37:17 -0700507 def get_prop(self, prop_name):
Josh Gao7f6e8dd2016-03-28 11:24:42 -0700508 output = split_lines(self.shell(['getprop', prop_name])[0])
Dan Albert32e13072015-09-18 13:37:17 -0700509 if len(output) != 1:
510 raise RuntimeError('Too many lines in getprop output:\n' +
511 '\n'.join(output))
512 value = output[0]
513 if not value.strip():
514 return None
515 return value
516
517 def set_prop(self, prop_name, value):
518 self.shell(['setprop', prop_name, value])