• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Provides a variety of device interactions based on adb.
6
7Eventually, this will be based on adb_wrapper.
8"""
9# pylint: disable=W0613
10
11import pipes
12import sys
13import time
14
15import pylib.android_commands
16from pylib.device import adb_wrapper
17from pylib.device import decorators
18from pylib.device import device_errors
19from pylib.utils import apk_helper
20from pylib.utils import parallelizer
21
22_DEFAULT_TIMEOUT = 30
23_DEFAULT_RETRIES = 3
24
25
26@decorators.WithExplicitTimeoutAndRetries(
27    _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
28def GetAVDs():
29  """Returns a list of Android Virtual Devices.
30
31  Returns:
32    A list containing the configured AVDs.
33  """
34  return pylib.android_commands.GetAVDs()
35
36
37@decorators.WithExplicitTimeoutAndRetries(
38    _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
39def RestartServer():
40  """Restarts the adb server.
41
42  Raises:
43    CommandFailedError if we fail to kill or restart the server.
44  """
45  pylib.android_commands.AndroidCommands().RestartAdbServer()
46
47
48class DeviceUtils(object):
49
50  def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT,
51               default_retries=_DEFAULT_RETRIES):
52    """DeviceUtils constructor.
53
54    Args:
55      device: Either a device serial, an existing AdbWrapper instance, an
56              an existing AndroidCommands instance, or nothing.
57      default_timeout: An integer containing the default number of seconds to
58                       wait for an operation to complete if no explicit value
59                       is provided.
60      default_retries: An integer containing the default number or times an
61                       operation should be retried on failure if no explicit
62                       value is provided.
63    """
64    self.old_interface = None
65    if isinstance(device, basestring):
66      self.old_interface = pylib.android_commands.AndroidCommands(device)
67    elif isinstance(device, adb_wrapper.AdbWrapper):
68      self.old_interface = pylib.android_commands.AndroidCommands(str(device))
69    elif isinstance(device, pylib.android_commands.AndroidCommands):
70      self.old_interface = device
71    elif not device:
72      self.old_interface = pylib.android_commands.AndroidCommands()
73    else:
74      raise ValueError('Unsupported type passed for argument "device"')
75    self._default_timeout = default_timeout
76    self._default_retries = default_retries
77    assert(hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR))
78    assert(hasattr(self, decorators.DEFAULT_RETRIES_ATTR))
79
80  @decorators.WithTimeoutAndRetriesFromInstance()
81  def IsOnline(self, timeout=None, retries=None):
82    """Checks whether the device is online.
83
84    Args:
85      timeout: timeout in seconds
86      retries: number of retries
87
88    Returns:
89      True if the device is online, False otherwise.
90
91    Raises:
92      CommandTimeoutError on timeout.
93    """
94    return self.old_interface.IsOnline()
95
96  @decorators.WithTimeoutAndRetriesFromInstance()
97  def HasRoot(self, timeout=None, retries=None):
98    """Checks whether or not adbd has root privileges.
99
100    Args:
101      timeout: timeout in seconds
102      retries: number of retries
103
104    Returns:
105      True if adbd has root privileges, False otherwise.
106
107    Raises:
108      CommandTimeoutError on timeout.
109      DeviceUnreachableError on missing device.
110    """
111    return self._HasRootImpl()
112
113  def _HasRootImpl(self):
114    """Implementation of HasRoot.
115
116    This is split from HasRoot to allow other DeviceUtils methods to call
117    HasRoot without spawning a new timeout thread.
118
119    Returns:
120      Same as for |HasRoot|.
121
122    Raises:
123      Same as for |HasRoot|.
124    """
125    return self.old_interface.IsRootEnabled()
126
127  @decorators.WithTimeoutAndRetriesFromInstance()
128  def EnableRoot(self, timeout=None, retries=None):
129    """Restarts adbd with root privileges.
130
131    Args:
132      timeout: timeout in seconds
133      retries: number of retries
134
135    Raises:
136      CommandFailedError if root could not be enabled.
137      CommandTimeoutError on timeout.
138    """
139    if not self.old_interface.EnableAdbRoot():
140      raise device_errors.CommandFailedError(
141          'Could not enable root.', device=str(self))
142
143  @decorators.WithTimeoutAndRetriesFromInstance()
144  def GetExternalStoragePath(self, timeout=None, retries=None):
145    """Get the device's path to its SD card.
146
147    Args:
148      timeout: timeout in seconds
149      retries: number of retries
150
151    Returns:
152      The device's path to its SD card.
153
154    Raises:
155      CommandFailedError if the external storage path could not be determined.
156      CommandTimeoutError on timeout.
157      DeviceUnreachableError on missing device.
158    """
159    try:
160      return self.old_interface.GetExternalStorage()
161    except AssertionError as e:
162      raise device_errors.CommandFailedError(
163          str(e), device=str(self)), None, sys.exc_info()[2]
164
165  @decorators.WithTimeoutAndRetriesFromInstance()
166  def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None):
167    """Wait for the device to fully boot.
168
169    This means waiting for the device to boot, the package manager to be
170    available, and the SD card to be ready. It can optionally mean waiting
171    for wifi to come up, too.
172
173    Args:
174      wifi: A boolean indicating if we should wait for wifi to come up or not.
175      timeout: timeout in seconds
176      retries: number of retries
177
178    Raises:
179      CommandFailedError on failure.
180      CommandTimeoutError if one of the component waits times out.
181      DeviceUnreachableError if the device becomes unresponsive.
182    """
183    self._WaitUntilFullyBootedImpl(wifi=wifi, timeout=timeout)
184
185  def _WaitUntilFullyBootedImpl(self, wifi=False, timeout=None):
186    """Implementation of WaitUntilFullyBooted.
187
188    This is split from WaitUntilFullyBooted to allow other DeviceUtils methods
189    to call WaitUntilFullyBooted without spawning a new timeout thread.
190
191    TODO(jbudorick) Remove the timeout parameter once this is no longer
192    implemented via AndroidCommands.
193
194    Args:
195      wifi: Same as for |WaitUntilFullyBooted|.
196      timeout: timeout in seconds
197
198    Raises:
199      Same as for |WaitUntilFullyBooted|.
200    """
201    if timeout is None:
202      timeout = self._default_timeout
203    self.old_interface.WaitForSystemBootCompleted(timeout)
204    self.old_interface.WaitForDevicePm()
205    self.old_interface.WaitForSdCardReady(timeout)
206    if wifi:
207      while not 'Wi-Fi is enabled' in (
208          self._RunShellCommandImpl('dumpsys wifi')):
209        time.sleep(1)
210
211  REBOOT_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
212  REBOOT_DEFAULT_RETRIES = _DEFAULT_RETRIES
213
214  @decorators.WithTimeoutAndRetriesDefaults(
215      REBOOT_DEFAULT_TIMEOUT,
216      REBOOT_DEFAULT_RETRIES)
217  def Reboot(self, block=True, timeout=None, retries=None):
218    """Reboot the device.
219
220    Args:
221      block: A boolean indicating if we should wait for the reboot to complete.
222      timeout: timeout in seconds
223      retries: number of retries
224
225    Raises:
226      CommandTimeoutError on timeout.
227      DeviceUnreachableError on missing device.
228    """
229    self.old_interface.Reboot()
230    if block:
231      self._WaitUntilFullyBootedImpl(timeout=timeout)
232
233  INSTALL_DEFAULT_TIMEOUT = 4 * _DEFAULT_TIMEOUT
234  INSTALL_DEFAULT_RETRIES = _DEFAULT_RETRIES
235
236  @decorators.WithTimeoutAndRetriesDefaults(
237      INSTALL_DEFAULT_TIMEOUT,
238      INSTALL_DEFAULT_RETRIES)
239  def Install(self, apk_path, reinstall=False, timeout=None, retries=None):
240    """Install an APK.
241
242    Noop if an identical APK is already installed.
243
244    Args:
245      apk_path: A string containing the path to the APK to install.
246      reinstall: A boolean indicating if we should keep any existing app data.
247      timeout: timeout in seconds
248      retries: number of retries
249
250    Raises:
251      CommandFailedError if the installation fails.
252      CommandTimeoutError if the installation times out.
253      DeviceUnreachableError on missing device.
254    """
255    package_name = apk_helper.GetPackageName(apk_path)
256    device_path = self.old_interface.GetApplicationPath(package_name)
257    if device_path is not None:
258      files_changed = self.old_interface.GetFilesChanged(
259          apk_path, device_path, ignore_filenames=True)
260      if len(files_changed) > 0:
261        should_install = True
262        if not reinstall:
263          out = self.old_interface.Uninstall(package_name)
264          for line in out.splitlines():
265            if 'Failure' in line:
266              raise device_errors.CommandFailedError(
267                  line.strip(), device=str(self))
268      else:
269        should_install = False
270    else:
271      should_install = True
272    if should_install:
273      try:
274        out = self.old_interface.Install(apk_path, reinstall=reinstall)
275        for line in out.splitlines():
276          if 'Failure' in line:
277            raise device_errors.CommandFailedError(
278                line.strip(), device=str(self))
279      except AssertionError as e:
280        raise device_errors.CommandFailedError(
281            str(e), device=str(self)), None, sys.exc_info()[2]
282
283  @decorators.WithTimeoutAndRetriesFromInstance()
284  def RunShellCommand(self, cmd, check_return=False, as_root=False,
285                      timeout=None, retries=None):
286    """Run an ADB shell command.
287
288    TODO(jbudorick) Switch the default value of check_return to True after
289    AndroidCommands is gone.
290
291    Args:
292      cmd: A list containing the command to run on the device and any arguments.
293      check_return: A boolean indicating whether or not the return code should
294                    be checked.
295      as_root: A boolean indicating whether the shell command should be run
296               with root privileges.
297      timeout: timeout in seconds
298      retries: number of retries
299
300    Returns:
301      The output of the command.
302
303    Raises:
304      CommandFailedError if check_return is True and the return code is nozero.
305      CommandTimeoutError on timeout.
306      DeviceUnreachableError on missing device.
307    """
308    return self._RunShellCommandImpl(cmd, check_return=check_return,
309                                     as_root=as_root, timeout=timeout)
310
311  def _RunShellCommandImpl(self, cmd, check_return=False, as_root=False,
312                           timeout=None):
313    """Implementation of RunShellCommand.
314
315    This is split from RunShellCommand to allow other DeviceUtils methods to
316    call RunShellCommand without spawning a new timeout thread.
317
318    TODO(jbudorick) Remove the timeout parameter once this is no longer
319    implemented via AndroidCommands.
320
321    Args:
322      cmd: Same as for |RunShellCommand|.
323      check_return: Same as for |RunShellCommand|.
324      as_root: Same as for |RunShellCommand|.
325      timeout: timeout in seconds
326
327    Raises:
328      Same as for |RunShellCommand|.
329
330    Returns:
331      Same as for |RunShellCommand|.
332    """
333    if isinstance(cmd, list):
334      cmd = ' '.join(cmd)
335    if as_root and not self.HasRoot():
336      cmd = 'su -c %s' % cmd
337    if check_return:
338      code, output = self.old_interface.GetShellCommandStatusAndOutput(
339          cmd, timeout_time=timeout)
340      if int(code) != 0:
341        raise device_errors.AdbCommandFailedError(
342            cmd.split(), 'Nonzero exit code (%d)' % code, device=str(self))
343    else:
344      output = self.old_interface.RunShellCommand(cmd, timeout_time=timeout)
345    return output
346
347  @decorators.WithTimeoutAndRetriesFromInstance()
348  def KillAll(self, process_name, signum=9, as_root=False, blocking=False,
349              timeout=None, retries=None):
350    """Kill all processes with the given name on the device.
351
352    Args:
353      process_name: A string containing the name of the process to kill.
354      signum: An integer containing the signal number to send to kill. Defaults
355              to 9 (SIGKILL).
356      as_root: A boolean indicating whether the kill should be executed with
357               root privileges.
358      blocking: A boolean indicating whether we should wait until all processes
359                with the given |process_name| are dead.
360      timeout: timeout in seconds
361      retries: number of retries
362
363    Raises:
364      CommandFailedError if no process was killed.
365      CommandTimeoutError on timeout.
366      DeviceUnreachableError on missing device.
367    """
368    pids = self._GetPidsImpl(process_name)
369    if not pids:
370      raise device_errors.CommandFailedError(
371          'No process "%s"' % process_name, device=str(self))
372
373    cmd = 'kill -%d %s' % (signum, ' '.join(pids.values()))
374    self._RunShellCommandImpl(cmd, as_root=as_root)
375
376    if blocking:
377      wait_period = 0.1
378      while self._GetPidsImpl(process_name):
379        time.sleep(wait_period)
380
381    return len(pids)
382
383  @decorators.WithTimeoutAndRetriesFromInstance()
384  def StartActivity(self, intent, blocking=False, trace_file_name=None,
385                    force_stop=False, timeout=None, retries=None):
386    """Start package's activity on the device.
387
388    Args:
389      intent: An Intent to send.
390      blocking: A boolean indicating whether we should wait for the activity to
391                finish launching.
392      trace_file_name: If present, a string that both indicates that we want to
393                       profile the activity and contains the path to which the
394                       trace should be saved.
395      force_stop: A boolean indicating whether we should stop the activity
396                  before starting it.
397      timeout: timeout in seconds
398      retries: number of retries
399
400    Raises:
401      CommandFailedError if the activity could not be started.
402      CommandTimeoutError on timeout.
403      DeviceUnreachableError on missing device.
404    """
405    single_category = (intent.category[0] if isinstance(intent.category, list)
406                                          else intent.category)
407    output = self.old_interface.StartActivity(
408        intent.package, intent.activity, wait_for_completion=blocking,
409        action=intent.action, category=single_category, data=intent.data,
410        extras=intent.extras, trace_file_name=trace_file_name,
411        force_stop=force_stop, flags=intent.flags)
412    for l in output:
413      if l.startswith('Error:'):
414        raise device_errors.CommandFailedError(l, device=str(self))
415
416  @decorators.WithTimeoutAndRetriesFromInstance()
417  def BroadcastIntent(self, intent, timeout=None, retries=None):
418    """Send a broadcast intent.
419
420    Args:
421      intent: An Intent to broadcast.
422      timeout: timeout in seconds
423      retries: number of retries
424
425    Raises:
426      CommandTimeoutError on timeout.
427      DeviceUnreachableError on missing device.
428    """
429    package, old_intent = intent.action.rsplit('.', 1)
430    if intent.extras is None:
431      args = []
432    else:
433      args = ['-e %s%s' % (k, ' "%s"' % v if v else '')
434              for k, v in intent.extras.items() if len(k) > 0]
435    self.old_interface.BroadcastIntent(package, old_intent, *args)
436
437  @decorators.WithTimeoutAndRetriesFromInstance()
438  def GoHome(self, timeout=None, retries=None):
439    """Return to the home screen.
440
441    Args:
442      timeout: timeout in seconds
443      retries: number of retries
444
445    Raises:
446      CommandTimeoutError on timeout.
447      DeviceUnreachableError on missing device.
448    """
449    self.old_interface.GoHome()
450
451  @decorators.WithTimeoutAndRetriesFromInstance()
452  def ForceStop(self, package, timeout=None, retries=None):
453    """Close the application.
454
455    Args:
456      package: A string containing the name of the package to stop.
457      timeout: timeout in seconds
458      retries: number of retries
459
460    Raises:
461      CommandTimeoutError on timeout.
462      DeviceUnreachableError on missing device.
463    """
464    self.old_interface.CloseApplication(package)
465
466  @decorators.WithTimeoutAndRetriesFromInstance()
467  def ClearApplicationState(self, package, timeout=None, retries=None):
468    """Clear all state for the given package.
469
470    Args:
471      package: A string containing the name of the package to stop.
472      timeout: timeout in seconds
473      retries: number of retries
474
475    Raises:
476      CommandTimeoutError on timeout.
477      DeviceUnreachableError on missing device.
478    """
479    self.old_interface.ClearApplicationState(package)
480
481  @decorators.WithTimeoutAndRetriesFromInstance()
482  def SendKeyEvent(self, keycode, timeout=None, retries=None):
483    """Sends a keycode to the device.
484
485    See: http://developer.android.com/reference/android/view/KeyEvent.html
486
487    Args:
488      keycode: A integer keycode to send to the device.
489      timeout: timeout in seconds
490      retries: number of retries
491
492    Raises:
493      CommandTimeoutError on timeout.
494      DeviceUnreachableError on missing device.
495    """
496    self.old_interface.SendKeyEvent(keycode)
497
498  PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
499  PUSH_CHANGED_FILES_DEFAULT_RETRIES = _DEFAULT_RETRIES
500
501  @decorators.WithTimeoutAndRetriesDefaults(
502      PUSH_CHANGED_FILES_DEFAULT_TIMEOUT,
503      PUSH_CHANGED_FILES_DEFAULT_RETRIES)
504  def PushChangedFiles(self, host_path, device_path, timeout=None,
505                       retries=None):
506    """Push files to the device, skipping files that don't need updating.
507
508    Args:
509      host_path: A string containing the absolute path to the file or directory
510                 on the host that should be minimally pushed to the device.
511      device_path: A string containing the absolute path of the destination on
512                   the device.
513      timeout: timeout in seconds
514      retries: number of retries
515
516    Raises:
517      CommandFailedError on failure.
518      CommandTimeoutError on timeout.
519      DeviceUnreachableError on missing device.
520    """
521    self.old_interface.PushIfNeeded(host_path, device_path)
522
523  @decorators.WithTimeoutAndRetriesFromInstance()
524  def FileExists(self, device_path, timeout=None, retries=None):
525    """Checks whether the given file exists on the device.
526
527    Args:
528      device_path: A string containing the absolute path to the file on the
529                   device.
530      timeout: timeout in seconds
531      retries: number of retries
532
533    Returns:
534      True if the file exists on the device, False otherwise.
535
536    Raises:
537      CommandTimeoutError on timeout.
538      DeviceUnreachableError on missing device.
539    """
540    return self._FileExistsImpl(device_path)
541
542  def _FileExistsImpl(self, device_path):
543    """Implementation of FileExists.
544
545    This is split from FileExists to allow other DeviceUtils methods to call
546    FileExists without spawning a new timeout thread.
547
548    Args:
549      device_path: Same as for |FileExists|.
550
551    Returns:
552      True if the file exists on the device, False otherwise.
553
554    Raises:
555      Same as for |FileExists|.
556    """
557    return self.old_interface.FileExistsOnDevice(device_path)
558
559  @decorators.WithTimeoutAndRetriesFromInstance()
560  def PullFile(self, device_path, host_path, timeout=None, retries=None):
561    """Pull a file from the device.
562
563    Args:
564      device_path: A string containing the absolute path of the file to pull
565                   from the device.
566      host_path: A string containing the absolute path of the destination on
567                 the host.
568      timeout: timeout in seconds
569      retries: number of retries
570
571    Raises:
572      CommandFailedError on failure.
573      CommandTimeoutError on timeout.
574    """
575    try:
576      self.old_interface.PullFileFromDevice(device_path, host_path)
577    except AssertionError as e:
578      raise device_errors.CommandFailedError(
579          str(e), device=str(self)), None, sys.exc_info()[2]
580
581  @decorators.WithTimeoutAndRetriesFromInstance()
582  def ReadFile(self, device_path, as_root=False, timeout=None, retries=None):
583    """Reads the contents of a file from the device.
584
585    Args:
586      device_path: A string containing the absolute path of the file to read
587                   from the device.
588      as_root: A boolean indicating whether the read should be executed with
589               root privileges.
590      timeout: timeout in seconds
591      retries: number of retries
592
593    Returns:
594      The contents of the file at |device_path| as a list of lines.
595
596    Raises:
597      CommandFailedError if the file can't be read.
598      CommandTimeoutError on timeout.
599      DeviceUnreachableError on missing device.
600    """
601    # TODO(jbudorick) Evaluate whether we awant to return a list of lines after
602    # the implementation switch, and if file not found should raise exception.
603    if as_root:
604      if not self.old_interface.CanAccessProtectedFileContents():
605        raise device_errors.CommandFailedError(
606          'Cannot read from %s with root privileges.' % device_path)
607      return self.old_interface.GetProtectedFileContents(device_path)
608    else:
609      return self.old_interface.GetFileContents(device_path)
610
611  @decorators.WithTimeoutAndRetriesFromInstance()
612  def WriteFile(self, device_path, contents, as_root=False, timeout=None,
613                retries=None):
614    """Writes |contents| to a file on the device.
615
616    Args:
617      device_path: A string containing the absolute path to the file to write
618                   on the device.
619      contents: A string containing the data to write to the device.
620      as_root: A boolean indicating whether the write should be executed with
621               root privileges.
622      timeout: timeout in seconds
623      retries: number of retries
624
625    Raises:
626      CommandFailedError if the file could not be written on the device.
627      CommandTimeoutError on timeout.
628      DeviceUnreachableError on missing device.
629    """
630    if as_root:
631      if not self.old_interface.CanAccessProtectedFileContents():
632        raise device_errors.CommandFailedError(
633            'Cannot write to %s with root privileges.' % device_path)
634      self.old_interface.SetProtectedFileContents(device_path, contents)
635    else:
636      self.old_interface.SetFileContents(device_path, contents)
637
638  @decorators.WithTimeoutAndRetriesFromInstance()
639  def WriteTextFile(self, device_path, text, as_root=False, timeout=None,
640                    retries=None):
641    """Writes |text| to a file on the device.
642
643    Assuming that |text| is a small string, this is typically more efficient
644    than |WriteFile|, as no files are pushed into the device.
645
646    Args:
647      device_path: A string containing the absolute path to the file to write
648                   on the device.
649      text: A short string of text to write to the file on the device.
650      as_root: A boolean indicating whether the write should be executed with
651               root privileges.
652      timeout: timeout in seconds
653      retries: number of retries
654
655    Raises:
656      CommandFailedError if the file could not be written on the device.
657      CommandTimeoutError on timeout.
658      DeviceUnreachableError on missing device.
659    """
660    self._RunShellCommandImpl('echo {1} > {0}'.format(device_path,
661        pipes.quote(text)), check_return=True, as_root=as_root)
662
663  @decorators.WithTimeoutAndRetriesFromInstance()
664  def Ls(self, device_path, timeout=None, retries=None):
665    """Lists the contents of a directory on the device.
666
667    Args:
668      device_path: A string containing the path of the directory on the device
669                   to list.
670      timeout: timeout in seconds
671      retries: number of retries
672
673    Returns:
674      The contents of the directory specified by |device_path|.
675
676    Raises:
677      CommandTimeoutError on timeout.
678      DeviceUnreachableError on missing device.
679    """
680    return self.old_interface.ListPathContents(device_path)
681
682  @decorators.WithTimeoutAndRetriesFromInstance()
683  def SetJavaAsserts(self, enabled, timeout=None, retries=None):
684    """Enables or disables Java asserts.
685
686    Args:
687      enabled: A boolean indicating whether Java asserts should be enabled
688               or disabled.
689      timeout: timeout in seconds
690      retries: number of retries
691
692    Returns:
693      True if the device-side property changed and a restart is required as a
694      result, False otherwise.
695
696    Raises:
697      CommandTimeoutError on timeout.
698    """
699    return self.old_interface.SetJavaAssertsEnabled(enabled)
700
701  @decorators.WithTimeoutAndRetriesFromInstance()
702  def GetProp(self, property_name, timeout=None, retries=None):
703    """Gets a property from the device.
704
705    Args:
706      property_name: A string containing the name of the property to get from
707                     the device.
708      timeout: timeout in seconds
709      retries: number of retries
710
711    Returns:
712      The value of the device's |property_name| property.
713
714    Raises:
715      CommandTimeoutError on timeout.
716    """
717    return self.old_interface.system_properties[property_name]
718
719  @decorators.WithTimeoutAndRetriesFromInstance()
720  def SetProp(self, property_name, value, timeout=None, retries=None):
721    """Sets a property on the device.
722
723    Args:
724      property_name: A string containing the name of the property to set on
725                     the device.
726      value: A string containing the value to set to the property on the
727             device.
728      timeout: timeout in seconds
729      retries: number of retries
730
731    Raises:
732      CommandTimeoutError on timeout.
733    """
734    self.old_interface.system_properties[property_name] = value
735
736  @decorators.WithTimeoutAndRetriesFromInstance()
737  def GetPids(self, process_name, timeout=None, retries=None):
738    """Returns the PIDs of processes with the given name.
739
740    Note that the |process_name| is often the package name.
741
742    Args:
743      process_name: A string containing the process name to get the PIDs for.
744      timeout: timeout in seconds
745      retries: number of retries
746
747    Returns:
748      A dict mapping process name to PID for each process that contained the
749      provided |process_name|.
750
751    Raises:
752      CommandTimeoutError on timeout.
753      DeviceUnreachableError on missing device.
754    """
755    return self._GetPidsImpl(process_name)
756
757  def _GetPidsImpl(self, process_name):
758    """Implementation of GetPids.
759
760    This is split from GetPids to allow other DeviceUtils methods to call
761    GetPids without spawning a new timeout thread.
762
763    Args:
764      process_name: A string containing the process name to get the PIDs for.
765
766    Returns:
767      A dict mapping process name to PID for each process that contained the
768      provided |process_name|.
769
770    Raises:
771      DeviceUnreachableError on missing device.
772    """
773    procs_pids = {}
774    for line in self._RunShellCommandImpl('ps'):
775      try:
776        ps_data = line.split()
777        if process_name in ps_data[-1]:
778          procs_pids[ps_data[-1]] = ps_data[1]
779      except IndexError:
780        pass
781    return procs_pids
782
783  @decorators.WithTimeoutAndRetriesFromInstance()
784  def TakeScreenshot(self, host_path=None, timeout=None, retries=None):
785    """Takes a screenshot of the device.
786
787    Args:
788      host_path: A string containing the path on the host to save the
789                 screenshot to. If None, a file name will be generated.
790      timeout: timeout in seconds
791      retries: number of retries
792
793    Returns:
794      The name of the file on the host to which the screenshot was saved.
795
796    Raises:
797      CommandFailedError on failure.
798      CommandTimeoutError on timeout.
799      DeviceUnreachableError on missing device.
800    """
801    return self.old_interface.TakeScreenshot(host_path)
802
803  @decorators.WithTimeoutAndRetriesFromInstance()
804  def GetIOStats(self, timeout=None, retries=None):
805    """Gets cumulative disk IO stats since boot for all processes.
806
807    Args:
808      timeout: timeout in seconds
809      retries: number of retries
810
811    Returns:
812      A dict containing |num_reads|, |num_writes|, |read_ms|, and |write_ms|.
813
814    Raises:
815      CommandTimeoutError on timeout.
816      DeviceUnreachableError on missing device.
817    """
818    return self.old_interface.GetIoStats()
819
820  @decorators.WithTimeoutAndRetriesFromInstance()
821  def GetMemoryUsageForPid(self, pid, timeout=None, retries=None):
822    """Gets the memory usage for the given PID.
823
824    Args:
825      pid: PID of the process.
826      timeout: timeout in seconds
827      retries: number of retries
828
829    Returns:
830      A 2-tuple containing:
831        - A dict containing the overall memory usage statistics for the PID.
832        - A dict containing memory usage statistics broken down by mapping.
833
834    Raises:
835      CommandTimeoutError on timeout.
836    """
837    return self.old_interface.GetMemoryUsageForPid(pid)
838
839  def __str__(self):
840    """Returns the device serial."""
841    s = self.old_interface.GetDevice()
842    if not s:
843      s = self.old_interface.Adb().GetSerialNumber()
844      if s == 'unknown':
845        raise device_errors.NoDevicesError()
846    return s
847
848  @staticmethod
849  def parallel(devices=None, async=False):
850    """Creates a Parallelizer to operate over the provided list of devices.
851
852    If |devices| is either |None| or an empty list, the Parallelizer will
853    operate over all attached devices.
854
855    Args:
856      devices: A list of either DeviceUtils instances or objects from
857               from which DeviceUtils instances can be constructed. If None,
858               all attached devices will be used.
859      async: If true, returns a Parallelizer that runs operations
860             asynchronously.
861
862    Returns:
863      A Parallelizer operating over |devices|.
864    """
865    if not devices or len(devices) == 0:
866      devices = pylib.android_commands.GetAttachedDevices()
867    parallelizer_type = (parallelizer.Parallelizer if async
868                         else parallelizer.SyncParallelizer)
869    return parallelizer_type([
870        d if isinstance(d, DeviceUtils) else DeviceUtils(d)
871        for d in devices])
872
873