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    logging.debug(
202        'cmd: %s, stdout: %s, stderr: %s, ret: %s',
203        utils.cli_cmd_to_string(args),
204        out,
205        err,
206        ret,
207    )
208    if ret == 0:
209      return out
210    else:
211      raise AdbError(
212          cmd=args, stdout=out, stderr=err, ret_code=ret, serial=self.serial
213      )
214
215  def _execute_and_process_stdout(self, args, shell, handler) -> bytes:
216    """Executes adb commands and processes the stdout with a handler.
217
218    Args:
219      args: string or list of strings, program arguments.
220        See subprocess.Popen() documentation.
221      shell: bool, True to run this command through the system shell,
222        False to invoke it directly. See subprocess.Popen() docs.
223      handler: func, a function to handle adb stdout line by line.
224
225    Returns:
226      The stderr of the adb command run if exit code is 0.
227
228    Raises:
229      AdbError: The adb command exit code is not 0.
230    """
231    proc = subprocess.Popen(
232        args,
233        stdout=subprocess.PIPE,
234        stderr=subprocess.PIPE,
235        shell=shell,
236        bufsize=1,
237    )
238    out = '[elided, processed via handler]'
239    try:
240      # Even if the process dies, stdout.readline still works
241      # and will continue until it runs out of stdout to process.
242      while True:
243        line = proc.stdout.readline()
244        if line:
245          handler(line)
246        else:
247          break
248    finally:
249      # Note, communicate will not contain any buffered output.
250      (unexpected_out, err) = proc.communicate()
251      if unexpected_out:
252        out = '[unexpected stdout] %s' % unexpected_out
253        for line in unexpected_out.splitlines():
254          handler(line)
255
256    ret = proc.returncode
257    logging.debug(
258        'cmd: %s, stdout: %s, stderr: %s, ret: %s',
259        utils.cli_cmd_to_string(args),
260        out,
261        err,
262        ret,
263    )
264    if ret == 0:
265      return err
266    else:
267      raise AdbError(cmd=args, stdout=out, stderr=err, ret_code=ret)
268
269  def _construct_adb_cmd(self, raw_name, args, shell):
270    """Constructs an adb command with arguments for a subprocess call.
271
272    Args:
273      raw_name: string, the raw unsanitized name of the adb command to
274        format.
275      args: string or list of strings, arguments to the adb command.
276        See subprocess.Proc() documentation.
277      shell: bool, True to run this command through the system shell,
278        False to invoke it directly. See subprocess.Proc() docs.
279
280    Returns:
281      The adb command in a format appropriate for subprocess. If shell is
282        True, then this is a string; otherwise, this is a list of
283        strings.
284    """
285    args = args or ''
286    name = raw_name.replace('_', '-')
287    if shell:
288      args = utils.cli_cmd_to_string(args)
289      # Add quotes around "adb" in case the ADB path contains spaces. This
290      # is pretty common on Windows (e.g. Program Files).
291      if self.serial:
292        adb_cmd = '"%s" -s "%s" %s %s' % (ADB, self.serial, name, args)
293      else:
294        adb_cmd = '"%s" %s %s' % (ADB, name, args)
295    else:
296      adb_cmd = [ADB]
297      if self.serial:
298        adb_cmd.extend(['-s', self.serial])
299      adb_cmd.append(name)
300      if args:
301        if isinstance(args, str):
302          adb_cmd.append(args)
303        else:
304          adb_cmd.extend(args)
305    return adb_cmd
306
307  def _exec_adb_cmd(self, name, args, shell, timeout, stderr) -> bytes:
308    adb_cmd = self._construct_adb_cmd(name, args, shell=shell)
309    out = self._exec_cmd(adb_cmd, shell=shell, timeout=timeout, stderr=stderr)
310    return out
311
312  def _execute_adb_and_process_stdout(
313      self, name, args, shell, handler
314  ) -> bytes:
315    adb_cmd = self._construct_adb_cmd(name, args, shell=shell)
316    err = self._execute_and_process_stdout(
317        adb_cmd, shell=shell, handler=handler
318    )
319    return err
320
321  def _parse_getprop_output(self, output):
322    """Parses the raw output of `adb shell getprop` into a dictionary.
323
324    Args:
325      output: byte str, the raw output of the `adb shell getprop` call.
326
327    Returns:
328      dict, name-value pairs of the properties.
329    """
330    output = output.decode('utf-8', errors='ignore').replace('\r\n', '\n')
331    results = {}
332    for line in output.split(']\n'):
333      if not line:
334        continue
335      try:
336        name, value = line.split(': ', 1)
337      except ValueError:
338        logging.debug('Failed to parse adb getprop line %s', line)
339        continue
340      name = name.strip()[1:-1]
341      # Remove any square bracket from either end of the value string.
342      if value and value[0] == '[':
343        value = value[1:]
344      results[name] = value
345    return results
346
347  @property
348  def current_user_id(self) -> int:
349    """The integer ID of the current Android user.
350
351    Some adb commands require specifying a user ID to work properly. Use
352    this to get the current user ID.
353
354    Note a "user" is not the same as an "account" in Android. See AOSP's
355    documentation for details.
356    https://source.android.com/devices/tech/admin/multi-user
357    """
358    sdk_int = int(self.getprop('ro.build.version.sdk'))
359    if sdk_int >= 24:
360      return int(self.shell(['am', 'get-current-user']))
361    if sdk_int >= 21:
362      user_info_str = self.shell(['dumpsys', 'user']).decode('utf-8')
363      return int(re.findall(r'\{(\d+):', user_info_str)[0])
364    # Multi-user is not supported in SDK < 21, only user 0 exists.
365    return 0
366
367  def connect(self, address) -> bytes:
368    """Executes the `adb connect` command with proper status checking.
369
370    Args:
371      address: string, the address of the Android instance to connect to.
372
373    Returns:
374      The stdout content.
375
376    Raises:
377      AdbError: if the connection failed.
378    """
379    stdout = self._exec_adb_cmd(
380        'connect', address, shell=False, timeout=None, stderr=None
381    )
382    if PATTERN_ADB_CONNECT_SUCCESS.match(stdout.decode('utf-8')) is None:
383      raise AdbError(
384          cmd=f'connect {address}', stdout=stdout, stderr='', ret_code=0
385      )
386    return stdout
387
388  def getprop(self, prop_name, timeout=DEFAULT_GETPROP_TIMEOUT_SEC):
389    """Get a property of the device.
390
391    This is a convenience wrapper for `adb shell getprop xxx`.
392
393    Args:
394      prop_name: A string that is the name of the property to get.
395      timeout: float, the number of seconds to wait before timing out.
396          If not specified, the DEFAULT_GETPROP_TIMEOUT_SEC is used.
397
398    Returns:
399      A string that is the value of the property, or None if the property
400      doesn't exist.
401    """
402    return (
403        self.shell(['getprop', prop_name], timeout=timeout)
404        .decode('utf-8')
405        .strip()
406    )
407
408  def getprops(self, prop_names):
409    """Get multiple properties of the device.
410
411    This is a convenience wrapper for `adb shell getprop`. Use this to
412    reduce the number of adb calls when getting multiple properties.
413
414    Args:
415      prop_names: list of strings, the names of the properties to get.
416
417    Returns:
418      A dict containing name-value pairs of the properties requested, if
419      they exist.
420    """
421    attempts = DEFAULT_GETPROPS_ATTEMPTS
422    results = {}
423    for attempt in range(attempts):
424      # The ADB getprop command can randomly return empty string, so try
425      # multiple times. This value should always be non-empty if the device
426      # in a working state.
427      raw_output = self.shell(['getprop'], timeout=DEFAULT_GETPROP_TIMEOUT_SEC)
428      properties = self._parse_getprop_output(raw_output)
429      if properties:
430        for name in prop_names:
431          if name in properties:
432            results[name] = properties[name]
433        break
434      # Don't call sleep on the last attempt.
435      if attempt < attempts - 1:
436        time.sleep(DEFAULT_GETPROPS_RETRY_SLEEP_SEC)
437    return results
438
439  def has_shell_command(self, command) -> bool:
440    """Checks to see if a given check command exists on the device.
441
442    Args:
443      command: A string that is the name of the command to check.
444
445    Returns:
446      A boolean that is True if the command exists and False otherwise.
447    """
448    try:
449      output = self.shell(['command', '-v', command]).decode('utf-8').strip()
450      return command in output
451    except AdbError:
452      # If the command doesn't exist, then 'command -v' can return
453      # an exit code > 1.
454      return False
455
456  def forward(self, args=None, shell=False) -> bytes:
457    with ADB_PORT_LOCK:
458      return self._exec_adb_cmd(
459          'forward', args, shell, timeout=None, stderr=None
460      )
461
462  def reverse(self, args=None, shell=False) -> bytes:
463    with ADB_PORT_LOCK:
464      return self._exec_adb_cmd(
465          'reverse', args, shell, timeout=None, stderr=None
466      )
467
468  def instrument(
469      self, package, options=None, runner=None, handler=None
470  ) -> bytes:
471    """Runs an instrumentation command on the device.
472
473    This is a convenience wrapper to avoid parameter formatting.
474
475    Example:
476
477    .. code-block:: python
478
479      device.instrument(
480        'com.my.package.test',
481        options = {
482          'class': 'com.my.package.test.TestSuite',
483        },
484      )
485
486    Args:
487      package: string, the package of the instrumentation tests.
488      options: dict, the instrumentation options including the test
489        class.
490      runner: string, the test runner name, which defaults to
491        DEFAULT_INSTRUMENTATION_RUNNER.
492      handler: optional func, when specified the function is used to parse
493        the instrumentation stdout line by line as the output is
494        generated; otherwise, the stdout is simply returned once the
495        instrumentation is finished.
496
497    Returns:
498      The stdout of instrumentation command or the stderr if the handler
499        is set.
500    """
501    if runner is None:
502      runner = DEFAULT_INSTRUMENTATION_RUNNER
503    if options is None:
504      options = {}
505
506    options_list = []
507    for option_key, option_value in options.items():
508      options_list.append('-e %s %s' % (option_key, option_value))
509    options_string = ' '.join(options_list)
510
511    instrumentation_command = 'am instrument -r -w %s %s/%s' % (
512        options_string,
513        package,
514        runner,
515    )
516    logging.info(
517        'AndroidDevice|%s: Executing adb shell %s',
518        self.serial,
519        instrumentation_command,
520    )
521    if handler is None:
522      return self._exec_adb_cmd(
523          'shell',
524          instrumentation_command,
525          shell=False,
526          timeout=None,
527          stderr=None,
528      )
529    else:
530      return self._execute_adb_and_process_stdout(
531          'shell', instrumentation_command, shell=False, handler=handler
532      )
533
534  def root(self) -> bytes:
535    """Enables ADB root mode on the device.
536
537    This method will retry to execute the command `adb root` when an
538    AdbError occurs, since sometimes the error `adb: unable to connect
539    for root: closed` is raised when executing `adb root` immediately after
540    the device is booted to OS.
541
542    Returns:
543      A string that is the stdout of root command.
544
545    Raises:
546      AdbError: If the command exit code is not 0.
547    """
548    retry_interval = ADB_ROOT_RETRY_ATTEMPT_INTERVAL_SEC
549    for attempt in range(ADB_ROOT_RETRY_ATTEMPTS):
550      try:
551        return self._exec_adb_cmd(
552            'root', args=None, shell=False, timeout=None, stderr=None
553        )
554      except AdbError as e:
555        if attempt + 1 < ADB_ROOT_RETRY_ATTEMPTS:
556          logging.debug(
557              'Retry the command "%s" since Error "%s" occurred.'
558              % (
559                  utils.cli_cmd_to_string(e.cmd),
560                  e.stderr.decode('utf-8').strip(),
561              )
562          )
563          # Buffer between "adb root" commands.
564          time.sleep(retry_interval)
565          retry_interval *= 2
566        else:
567          raise e
568
569  def __getattr__(self, name):
570    def adb_call(args=None, shell=False, timeout=None, stderr=None) -> bytes:
571      """Wrapper for an ADB command.
572
573      Args:
574        args: string or list of strings, arguments to the adb command.
575          See subprocess.Proc() documentation.
576        shell: bool, True to run this command through the system shell,
577          False to invoke it directly. See subprocess.Proc() docs.
578        timeout: float, the number of seconds to wait before timing out.
579          If not specified, no timeout takes effect.
580        stderr: a Byte stream, like io.BytesIO, stderr of the command
581          will be written to this object if provided.
582
583      Returns:
584        The output of the adb command run if exit code is 0.
585      """
586      return self._exec_adb_cmd(
587          name, args, shell=shell, timeout=timeout, stderr=stderr
588      )
589
590    return adb_call
591