• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import logging
16import re
17import subprocess
18import threading
19import time
20
21from mobly import utils
22
23# Command to use for running ADB commands.
24ADB = 'adb'
25
26# adb gets confused if we try to manage bound ports in parallel, so anything to
27# do with port forwarding must happen under this lock.
28ADB_PORT_LOCK = threading.Lock()
29
30# Number of attempts to execute "adb root", and seconds for interval time of
31# this commands.
32ADB_ROOT_RETRY_ATTEMPTS = 3
33ADB_ROOT_RETRY_ATTEMPT_INTERVAL_SEC = 10
34
35# Qualified class name of the default instrumentation test runner.
36DEFAULT_INSTRUMENTATION_RUNNER = 'com.android.common.support.test.runner.AndroidJUnitRunner'
37
38# `adb shell getprop` can take surprisingly long, when the device is a
39# networked virtual device.
40DEFAULT_GETPROP_TIMEOUT_SEC = 10
41DEFAULT_GETPROPS_ATTEMPTS = 3
42DEFAULT_GETPROPS_RETRY_SLEEP_SEC = 1
43
44# The regex pattern indicating the `adb connect` command did not fail.
45PATTERN_ADB_CONNECT_SUCCESS = re.compile(
46    r'^connected to .*|^already connected to .*')
47
48
49class Error(Exception):
50  """Base error type for adb proxy module."""
51
52
53class AdbError(Error):
54  """Raised when an adb command encounters an error.
55
56  Attributes:
57    cmd: list of strings, the adb command executed.
58    stdout: byte string, the raw stdout of the command.
59    stderr: byte string, the raw stderr of the command.
60    ret_code: int, the return code of the command.
61    serial: string, the serial of the device the command is executed on.
62      This is an empty string if the adb command is not specific to a
63      device.
64  """
65
66  def __init__(self, cmd, stdout, stderr, ret_code, serial=''):
67    super().__init__()
68    self.cmd = cmd
69    self.stdout = stdout
70    self.stderr = stderr
71    self.ret_code = ret_code
72    self.serial = serial
73
74  def __str__(self):
75    return ('Error executing adb cmd "%s". ret: %d, stdout: %s, stderr: %s') % (
76        utils.cli_cmd_to_string(
77            self.cmd), self.ret_code, self.stdout, self.stderr)
78
79
80class AdbTimeoutError(Error):
81  """Raised when an command did not complete within expected time.
82
83  Attributes:
84    cmd: list of strings, the adb command that timed out
85    timeout: float, the number of seconds passed before timing out.
86    serial: string, the serial of the device the command is executed on.
87      This is an empty string if the adb command is not specific to a
88      device.
89  """
90
91  def __init__(self, cmd, timeout, serial=''):
92    super().__init__()
93    self.cmd = cmd
94    self.timeout = timeout
95    self.serial = serial
96
97  def __str__(self):
98    return 'Timed out executing command "%s" after %ss.' % (
99        utils.cli_cmd_to_string(self.cmd), self.timeout)
100
101
102def is_adb_available():
103  """Checks if adb is available as a command line tool.
104
105  Returns:
106    True if adb binary is available in console, False otherwise.
107  """
108  ret, out, err = utils.run_command('which adb', shell=True)
109  clean_out = out.decode('utf-8').strip()
110  if clean_out:
111    return True
112  return False
113
114
115def list_occupied_adb_ports():
116  """Lists all the host ports occupied by adb forward.
117
118  This is useful because adb will silently override the binding if an attempt
119  to bind to a port already used by adb was made, instead of throwing binding
120  error. So one should always check what ports adb is using before trying to
121  bind to a port with adb.
122
123  Returns:
124    A list of integers representing occupied host ports.
125  """
126  out = AdbProxy().forward('--list')
127  clean_lines = str(out, 'utf-8').strip().split('\n')
128  used_ports = []
129  for line in clean_lines:
130    tokens = line.split(' tcp:')
131    if len(tokens) != 3:
132      continue
133    used_ports.append(int(tokens[1]))
134  return used_ports
135
136
137class AdbProxy:
138  """Proxy class for ADB.
139
140  For syntactic reasons, the '-' in adb commands need to be replaced with
141  '_'. Can directly execute adb commands on an object:
142  >> adb = AdbProxy(<serial>)
143  >> adb.start_server()
144  >> adb.devices() # will return the console output of "adb devices".
145
146  By default, command args are expected to be an iterable which is passed
147  directly to subprocess.Popen():
148  >> adb.shell(['echo', 'a', 'b'])
149
150  This way of launching commands is recommended by the subprocess
151  documentation to avoid shell injection vulnerabilities and avoid having to
152  deal with multiple layers of shell quoting and different shell environments
153  between different OSes.
154
155  If you really want to run the command through the system shell, this is
156  possible by supplying shell=True, but try to avoid this if possible:
157  >> adb.shell('cat /foo > /tmp/file', shell=True)
158  """
159
160  def __init__(self, serial=''):
161    self.serial = serial
162
163  def _exec_cmd(self, args, shell, timeout, stderr) -> bytes:
164    """Executes adb commands.
165
166    Args:
167      args: string or list of strings, program arguments.
168        See subprocess.Popen() documentation.
169      shell: bool, True to run this command through the system shell,
170        False to invoke it directly. See subprocess.Popen() docs.
171      timeout: float, the number of seconds to wait before timing out.
172        If not specified, no timeout takes effect.
173      stderr: a Byte stream, like io.BytesIO, stderr of the command will
174        be written to this object if provided.
175
176    Returns:
177      The output of the adb command run if exit code is 0.
178
179    Raises:
180      ValueError: timeout value is invalid.
181      AdbError: The adb command exit code is not 0.
182      AdbTimeoutError: The adb command timed out.
183    """
184    if timeout and timeout <= 0:
185      raise ValueError('Timeout is not a positive value: %s' % timeout)
186    try:
187      (ret, out, err) = utils.run_command(args, shell=shell, timeout=timeout)
188    except subprocess.TimeoutExpired:
189      raise AdbTimeoutError(cmd=args, timeout=timeout, serial=self.serial)
190
191    if stderr:
192      stderr.write(err)
193    logging.debug('cmd: %s, stdout: %s, stderr: %s, ret: %s',
194                  utils.cli_cmd_to_string(args), out, err, ret)
195    if ret == 0:
196      return out
197    else:
198      raise AdbError(cmd=args,
199                     stdout=out,
200                     stderr=err,
201                     ret_code=ret,
202                     serial=self.serial)
203
204  def _execute_and_process_stdout(self, args, shell, handler) -> bytes:
205    """Executes adb commands and processes the stdout with a handler.
206
207    Args:
208      args: string or list of strings, program arguments.
209        See subprocess.Popen() documentation.
210      shell: bool, True to run this command through the system shell,
211        False to invoke it directly. See subprocess.Popen() docs.
212      handler: func, a function to handle adb stdout line by line.
213
214    Returns:
215      The stderr of the adb command run if exit code is 0.
216
217    Raises:
218      AdbError: The adb command exit code is not 0.
219    """
220    proc = subprocess.Popen(args,
221                            stdout=subprocess.PIPE,
222                            stderr=subprocess.PIPE,
223                            shell=shell,
224                            bufsize=1)
225    out = '[elided, processed via handler]'
226    try:
227      # Even if the process dies, stdout.readline still works
228      # and will continue until it runs out of stdout to process.
229      while True:
230        line = proc.stdout.readline()
231        if line:
232          handler(line)
233        else:
234          break
235    finally:
236      # Note, communicate will not contain any buffered output.
237      (unexpected_out, err) = proc.communicate()
238      if unexpected_out:
239        out = '[unexpected stdout] %s' % unexpected_out
240        for line in unexpected_out.splitlines():
241          handler(line)
242
243    ret = proc.returncode
244    logging.debug('cmd: %s, stdout: %s, stderr: %s, ret: %s',
245                  utils.cli_cmd_to_string(args), out, err, ret)
246    if ret == 0:
247      return err
248    else:
249      raise AdbError(cmd=args, stdout=out, stderr=err, ret_code=ret)
250
251  def _construct_adb_cmd(self, raw_name, args, shell):
252    """Constructs an adb command with arguments for a subprocess call.
253
254    Args:
255      raw_name: string, the raw unsanitized name of the adb command to
256        format.
257      args: string or list of strings, arguments to the adb command.
258        See subprocess.Proc() documentation.
259      shell: bool, True to run this command through the system shell,
260        False to invoke it directly. See subprocess.Proc() docs.
261
262    Returns:
263      The adb command in a format appropriate for subprocess. If shell is
264        True, then this is a string; otherwise, this is a list of
265        strings.
266    """
267    args = args or ''
268    name = raw_name.replace('_', '-')
269    if shell:
270      args = utils.cli_cmd_to_string(args)
271      # Add quotes around "adb" in case the ADB path contains spaces. This
272      # is pretty common on Windows (e.g. Program Files).
273      if self.serial:
274        adb_cmd = '"%s" -s "%s" %s %s' % (ADB, self.serial, name, args)
275      else:
276        adb_cmd = '"%s" %s %s' % (ADB, name, args)
277    else:
278      adb_cmd = [ADB]
279      if self.serial:
280        adb_cmd.extend(['-s', self.serial])
281      adb_cmd.append(name)
282      if args:
283        if isinstance(args, str):
284          adb_cmd.append(args)
285        else:
286          adb_cmd.extend(args)
287    return adb_cmd
288
289  def _exec_adb_cmd(self, name, args, shell, timeout, stderr) -> bytes:
290    adb_cmd = self._construct_adb_cmd(name, args, shell=shell)
291    out = self._exec_cmd(adb_cmd, shell=shell, timeout=timeout, stderr=stderr)
292    return out
293
294  def _execute_adb_and_process_stdout(self, name, args, shell,
295                                      handler) -> bytes:
296    adb_cmd = self._construct_adb_cmd(name, args, shell=shell)
297    err = self._execute_and_process_stdout(adb_cmd,
298                                           shell=shell,
299                                           handler=handler)
300    return err
301
302  def _parse_getprop_output(self, output):
303    """Parses the raw output of `adb shell getprop` into a dictionary.
304
305    Args:
306      output: byte str, the raw output of the `adb shell getprop` call.
307
308    Returns:
309      dict, name-value pairs of the properties.
310    """
311    output = output.decode('utf-8', errors='ignore').replace('\r\n', '\n')
312    results = {}
313    for line in output.split(']\n'):
314      if not line:
315        continue
316      try:
317        name, value = line.split(': ', 1)
318      except ValueError:
319        logging.debug('Failed to parse adb getprop line %s', line)
320        continue
321      name = name.strip()[1:-1]
322      # Remove any square bracket from either end of the value string.
323      if value and value[0] == '[':
324        value = value[1:]
325      results[name] = value
326    return results
327
328  @property
329  def current_user_id(self) -> int:
330    """The integer ID of the current Android user.
331
332    Some adb commands require specifying a user ID to work properly. Use
333    this to get the current user ID.
334
335    Note a "user" is not the same as an "account" in Android. See AOSP's
336    documentation for details.
337    https://source.android.com/devices/tech/admin/multi-user
338    """
339    sdk_int = int(self.getprop('ro.build.version.sdk'))
340    if sdk_int >= 24:
341      return int(self.shell(['am', 'get-current-user']))
342    if sdk_int >= 21:
343      user_info_str = self.shell(['dumpsys', 'user']).decode('utf-8')
344      return int(re.findall(r'\{(\d+):', user_info_str)[0])
345    # Multi-user is not supported in SDK < 21, only user 0 exists.
346    return 0
347
348  def connect(self, address) -> bytes:
349    """Executes the `adb connect` command with proper status checking.
350
351    Args:
352      address: string, the address of the Android instance to connect to.
353
354    Returns:
355      The stdout content.
356
357    Raises:
358      AdbError: if the connection failed.
359    """
360    stdout = self._exec_adb_cmd('connect',
361                                address,
362                                shell=False,
363                                timeout=None,
364                                stderr=None)
365    if PATTERN_ADB_CONNECT_SUCCESS.match(stdout.decode('utf-8')) is None:
366      raise AdbError(cmd=f'connect {address}',
367                     stdout=stdout,
368                     stderr='',
369                     ret_code=0)
370    return stdout
371
372  def getprop(self, prop_name, timeout=DEFAULT_GETPROP_TIMEOUT_SEC):
373    """Get a property of the device.
374
375    This is a convenience wrapper for `adb shell getprop xxx`.
376
377    Args:
378      prop_name: A string that is the name of the property to get.
379      timeout: float, the number of seconds to wait before timing out.
380          If not specified, the DEFAULT_GETPROP_TIMEOUT_SEC is used.
381
382    Returns:
383      A string that is the value of the property, or None if the property
384      doesn't exist.
385    """
386    return self.shell(['getprop', prop_name],
387                      timeout=timeout).decode('utf-8').strip()
388
389  def getprops(self, prop_names):
390    """Get multiple properties of the device.
391
392    This is a convenience wrapper for `adb shell getprop`. Use this to
393    reduce the number of adb calls when getting multiple properties.
394
395    Args:
396      prop_names: list of strings, the names of the properties to get.
397
398    Returns:
399      A dict containing name-value pairs of the properties requested, if
400      they exist.
401    """
402    attempts = DEFAULT_GETPROPS_ATTEMPTS
403    results = {}
404    for attempt in range(attempts):
405      # The ADB getprop command can randomly return empty string, so try
406      # multiple times. This value should always be non-empty if the device
407      # in a working state.
408      raw_output = self.shell(['getprop'], timeout=DEFAULT_GETPROP_TIMEOUT_SEC)
409      properties = self._parse_getprop_output(raw_output)
410      if properties:
411        for name in prop_names:
412          if name in properties:
413            results[name] = properties[name]
414        break
415      # Don't call sleep on the last attempt.
416      if attempt < attempts - 1:
417        time.sleep(DEFAULT_GETPROPS_RETRY_SLEEP_SEC)
418    return results
419
420  def has_shell_command(self, command) -> bool:
421    """Checks to see if a given check command exists on the device.
422
423    Args:
424      command: A string that is the name of the command to check.
425
426    Returns:
427      A boolean that is True if the command exists and False otherwise.
428    """
429    try:
430      output = self.shell(['command', '-v', command]).decode('utf-8').strip()
431      return command in output
432    except AdbError:
433      # If the command doesn't exist, then 'command -v' can return
434      # an exit code > 1.
435      return False
436
437  def forward(self, args=None, shell=False) -> bytes:
438    with ADB_PORT_LOCK:
439      return self._exec_adb_cmd('forward',
440                                args,
441                                shell,
442                                timeout=None,
443                                stderr=None)
444
445  def instrument(self,
446                 package,
447                 options=None,
448                 runner=None,
449                 handler=None) -> bytes:
450    """Runs an instrumentation command on the device.
451
452    This is a convenience wrapper to avoid parameter formatting.
453
454    Example:
455
456    .. code-block:: python
457
458      device.instrument(
459        'com.my.package.test',
460        options = {
461          'class': 'com.my.package.test.TestSuite',
462        },
463      )
464
465    Args:
466      package: string, the package of the instrumentation tests.
467      options: dict, the instrumentation options including the test
468        class.
469      runner: string, the test runner name, which defaults to
470        DEFAULT_INSTRUMENTATION_RUNNER.
471      handler: optional func, when specified the function is used to parse
472        the instrumentation stdout line by line as the output is
473        generated; otherwise, the stdout is simply returned once the
474        instrumentation is finished.
475
476    Returns:
477      The stdout of instrumentation command or the stderr if the handler
478        is set.
479    """
480    if runner is None:
481      runner = DEFAULT_INSTRUMENTATION_RUNNER
482    if options is None:
483      options = {}
484
485    options_list = []
486    for option_key, option_value in options.items():
487      options_list.append('-e %s %s' % (option_key, option_value))
488    options_string = ' '.join(options_list)
489
490    instrumentation_command = 'am instrument -r -w %s %s/%s' % (options_string,
491                                                                package, runner)
492    logging.info('AndroidDevice|%s: Executing adb shell %s', self.serial,
493                 instrumentation_command)
494    if handler is None:
495      return self._exec_adb_cmd('shell',
496                                instrumentation_command,
497                                shell=False,
498                                timeout=None,
499                                stderr=None)
500    else:
501      return self._execute_adb_and_process_stdout('shell',
502                                                  instrumentation_command,
503                                                  shell=False,
504                                                  handler=handler)
505
506  def root(self) -> bytes:
507    """Enables ADB root mode on the device.
508
509    This method will retry to execute the command `adb root` when an
510    AdbError occurs, since sometimes the error `adb: unable to connect
511    for root: closed` is raised when executing `adb root` immediately after
512    the device is booted to OS.
513
514    Returns:
515      A string that is the stdout of root command.
516
517    Raises:
518      AdbError: If the command exit code is not 0.
519    """
520    retry_interval = ADB_ROOT_RETRY_ATTEMPT_INTERVAL_SEC
521    for attempt in range(ADB_ROOT_RETRY_ATTEMPTS):
522      try:
523        return self._exec_adb_cmd('root',
524                                  args=None,
525                                  shell=False,
526                                  timeout=None,
527                                  stderr=None)
528      except AdbError as e:
529        if attempt + 1 < ADB_ROOT_RETRY_ATTEMPTS:
530          logging.debug('Retry the command "%s" since Error "%s" occurred.' %
531                        (utils.cli_cmd_to_string(
532                            e.cmd), e.stderr.decode('utf-8').strip()))
533          # Buffer between "adb root" commands.
534          time.sleep(retry_interval)
535          retry_interval *= 2
536        else:
537          raise e
538
539  def __getattr__(self, name):
540
541    def adb_call(args=None, shell=False, timeout=None, stderr=None) -> bytes:
542      """Wrapper for an ADB command.
543
544      Args:
545        args: string or list of strings, arguments to the adb command.
546          See subprocess.Proc() documentation.
547        shell: bool, True to run this command through the system shell,
548          False to invoke it directly. See subprocess.Proc() docs.
549        timeout: float, the number of seconds to wait before timing out.
550          If not specified, no timeout takes effect.
551        stderr: a Byte stream, like io.BytesIO, stderr of the command
552          will be written to this object if provided.
553
554      Returns:
555        The output of the adb command run if exit code is 0.
556      """
557      return self._exec_adb_cmd(name,
558                                args,
559                                shell=shell,
560                                timeout=timeout,
561                                stderr=stderr)
562
563    return adb_call
564