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