1# Copyright 2013 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
5import logging
6import os
7import re
8import subprocess
9import tempfile
10
11from telemetry.core import android_platform
12from telemetry.core import exceptions
13from telemetry.core import util
14from telemetry import decorators
15from telemetry.internal.forwarders import android_forwarder
16from telemetry.internal.image_processing import video
17from telemetry.internal.platform import android_device
18from telemetry.internal.platform import linux_based_platform_backend
19from telemetry.internal.platform.power_monitor import android_dumpsys_power_monitor
20from telemetry.internal.platform.power_monitor import android_fuelgauge_power_monitor
21from telemetry.internal.platform.power_monitor import android_temperature_monitor
22from telemetry.internal.platform.power_monitor import monsoon_power_monitor
23from telemetry.internal.platform.power_monitor import (
24  android_power_monitor_controller)
25from telemetry.internal.platform.power_monitor import sysfs_power_monitor
26from telemetry.internal.platform.profiler import android_prebuilt_profiler_helper
27from telemetry.internal.util import external_modules
28
29psutil = external_modules.ImportOptionalModule('psutil')
30import adb_install_cert
31
32from devil.android import app_ui
33from devil.android import battery_utils
34from devil.android import device_errors
35from devil.android import device_utils
36from devil.android.perf import cache_control
37from devil.android.perf import perf_control
38from devil.android.perf import thermal_throttle
39from devil.android.sdk import version_codes
40from devil.android.tools import video_recorder
41
42try:
43  from devil.android.perf import surface_stats_collector
44except Exception:
45  surface_stats_collector = None
46
47
48_DEVICE_COPY_SCRIPT_FILE = os.path.abspath(os.path.join(
49    os.path.dirname(__file__), 'efficient_android_directory_copy.sh'))
50_DEVICE_COPY_SCRIPT_LOCATION = (
51    '/data/local/tmp/efficient_android_directory_copy.sh')
52
53# TODO(nednguyen): Remove this method and update the client config to point to
54# the correct binary instead.
55def _FindLocallyBuiltPath(binary_name):
56  """Finds the most recently built |binary_name|."""
57  command = None
58  command_mtime = 0
59  required_mode = os.X_OK
60  if binary_name.endswith('.apk'):
61    required_mode = os.R_OK
62  for build_path in util.GetBuildDirectories():
63    candidate = os.path.join(build_path, binary_name)
64    if os.path.isfile(candidate) and os.access(candidate, required_mode):
65      candidate_mtime = os.stat(candidate).st_mtime
66      if candidate_mtime > command_mtime:
67        command = candidate
68        command_mtime = candidate_mtime
69  return command
70
71
72class AndroidPlatformBackend(
73    linux_based_platform_backend.LinuxBasedPlatformBackend):
74  def __init__(self, device):
75    assert device, (
76        'AndroidPlatformBackend can only be initialized from remote device')
77    super(AndroidPlatformBackend, self).__init__(device)
78    self._device = device_utils.DeviceUtils(device.device_id)
79    # Trying to root the device, if possible.
80    if not self._device.HasRoot():
81      try:
82        self._device.EnableRoot()
83      except device_errors.CommandFailedError:
84        logging.warning('Unable to root %s', str(self._device))
85    self._battery = battery_utils.BatteryUtils(self._device)
86    self._enable_performance_mode = device.enable_performance_mode
87    self._surface_stats_collector = None
88    self._perf_tests_setup = perf_control.PerfControl(self._device)
89    self._thermal_throttle = thermal_throttle.ThermalThrottle(self._device)
90    self._raw_display_frame_rate_measurements = []
91    try:
92      self._can_access_protected_file_contents = (
93          self._device.HasRoot() or self._device.NeedsSU())
94    except Exception:
95      logging.exception('New exception caused by DeviceUtils conversion')
96      raise
97    self._device_copy_script = None
98    self._power_monitor = (
99      android_power_monitor_controller.AndroidPowerMonitorController([
100        android_temperature_monitor.AndroidTemperatureMonitor(self._device),
101        monsoon_power_monitor.MonsoonPowerMonitor(self._device, self),
102        android_dumpsys_power_monitor.DumpsysPowerMonitor(
103          self._battery, self),
104        sysfs_power_monitor.SysfsPowerMonitor(self, standalone=True),
105        android_fuelgauge_power_monitor.FuelGaugePowerMonitor(
106            self._battery),
107    ], self._battery))
108    self._video_recorder = None
109    self._installed_applications = None
110
111    self._device_cert_util = None
112    self._system_ui = None
113
114    _FixPossibleAdbInstability()
115
116  @property
117  def log_file_path(self):
118    return None
119
120  @classmethod
121  def SupportsDevice(cls, device):
122    return isinstance(device, android_device.AndroidDevice)
123
124  @classmethod
125  def CreatePlatformForDevice(cls, device, finder_options):
126    assert cls.SupportsDevice(device)
127    platform_backend = AndroidPlatformBackend(device)
128    return android_platform.AndroidPlatform(platform_backend)
129
130  @property
131  def forwarder_factory(self):
132    if not self._forwarder_factory:
133      self._forwarder_factory = android_forwarder.AndroidForwarderFactory(
134          self._device)
135
136    return self._forwarder_factory
137
138  @property
139  def device(self):
140    return self._device
141
142  def GetSystemUi(self):
143    if self._system_ui is None:
144      self._system_ui = app_ui.AppUi(self.device, 'com.android.systemui')
145    return self._system_ui
146
147  def IsSvelte(self):
148    description = self._device.GetProp('ro.build.description', cache=True)
149    if description is not None:
150      return 'svelte' in description
151    else:
152      return False
153
154  def IsDisplayTracingSupported(self):
155    return bool(self.GetOSVersionName() >= 'J')
156
157  def StartDisplayTracing(self):
158    assert not self._surface_stats_collector
159    # Clear any leftover data from previous timed out tests
160    self._raw_display_frame_rate_measurements = []
161    self._surface_stats_collector = \
162        surface_stats_collector.SurfaceStatsCollector(self._device)
163    self._surface_stats_collector.Start()
164
165  def StopDisplayTracing(self):
166    if not self._surface_stats_collector:
167      return
168
169    try:
170      refresh_period, timestamps = self._surface_stats_collector.Stop()
171      pid = self._surface_stats_collector.GetSurfaceFlingerPid()
172    finally:
173      self._surface_stats_collector = None
174    # TODO(sullivan): should this code be inline, or live elsewhere?
175    events = []
176    for ts in timestamps:
177      events.append({
178        'cat': 'SurfaceFlinger',
179        'name': 'vsync_before',
180        'ts': ts,
181        'pid': pid,
182        'tid': pid,
183        'args': {'data': {
184          'frame_count': 1,
185          'refresh_period': refresh_period,
186        }}
187      })
188    return events
189
190  def CanTakeScreenshot(self):
191    return True
192
193  def TakeScreenshot(self, file_path):
194    return bool(self._device.TakeScreenshot(host_path=file_path))
195
196  def SetFullPerformanceModeEnabled(self, enabled):
197    if not self._enable_performance_mode:
198      logging.warning('CPU governor will not be set!')
199      return
200    if enabled:
201      self._perf_tests_setup.SetHighPerfMode()
202    else:
203      self._perf_tests_setup.SetDefaultPerfMode()
204
205  def CanMonitorThermalThrottling(self):
206    return True
207
208  def IsThermallyThrottled(self):
209    return self._thermal_throttle.IsThrottled()
210
211  def HasBeenThermallyThrottled(self):
212    return self._thermal_throttle.HasBeenThrottled()
213
214  def GetCpuStats(self, pid):
215    if not self._can_access_protected_file_contents:
216      logging.warning('CPU stats cannot be retrieved on non-rooted device.')
217      return {}
218    return super(AndroidPlatformBackend, self).GetCpuStats(pid)
219
220  def GetCpuTimestamp(self):
221    if not self._can_access_protected_file_contents:
222      logging.warning('CPU timestamp cannot be retrieved on non-rooted device.')
223      return {}
224    return super(AndroidPlatformBackend, self).GetCpuTimestamp()
225
226  def SetGraphicsMemoryTrackingEnabled(self, enabled):
227    if not enabled:
228      self.KillApplication('memtrack_helper')
229      return
230
231    if not android_prebuilt_profiler_helper.InstallOnDevice(
232        self._device, 'memtrack_helper'):
233      raise Exception('Error installing memtrack_helper.')
234    try:
235      cmd = android_prebuilt_profiler_helper.GetDevicePath('memtrack_helper')
236      cmd += ' -d'
237      self._device.RunShellCommand(cmd, as_root=True, check_return=True)
238    except Exception:
239      logging.exception('New exception caused by DeviceUtils conversion')
240      raise
241
242  def PurgeUnpinnedMemory(self):
243    """Purges the unpinned ashmem memory for the whole system.
244
245    This can be used to make memory measurements more stable. Requires root.
246    """
247    if not self._can_access_protected_file_contents:
248      logging.warning('Cannot run purge_ashmem. Requires a rooted device.')
249      return
250
251    if not android_prebuilt_profiler_helper.InstallOnDevice(
252        self._device, 'purge_ashmem'):
253      raise Exception('Error installing purge_ashmem.')
254    try:
255      output = self._device.RunShellCommand(
256          android_prebuilt_profiler_helper.GetDevicePath('purge_ashmem'))
257    except Exception:
258      logging.exception('New exception caused by DeviceUtils conversion')
259      raise
260    for l in output:
261      logging.info(l)
262
263  def GetMemoryStats(self, pid):
264    memory_usage = self._device.GetMemoryUsageForPid(pid)
265    if not memory_usage:
266      return {}
267    return {'ProportionalSetSize': memory_usage['Pss'] * 1024,
268            'SharedDirty': memory_usage['Shared_Dirty'] * 1024,
269            'PrivateDirty': memory_usage['Private_Dirty'] * 1024,
270            'VMPeak': memory_usage['VmHWM'] * 1024}
271
272  def GetChildPids(self, pid):
273    child_pids = []
274    ps = self.GetPsOutput(['pid', 'name'])
275    for curr_pid, curr_name in ps:
276      if int(curr_pid) == pid:
277        name = curr_name
278        for curr_pid, curr_name in ps:
279          if curr_name.startswith(name) and curr_name != name:
280            child_pids.append(int(curr_pid))
281        break
282    return child_pids
283
284  @decorators.Cache
285  def GetCommandLine(self, pid):
286    ps = self.GetPsOutput(['pid', 'name'], pid)
287    if not ps:
288      raise exceptions.ProcessGoneException()
289    return ps[0][1]
290
291  @decorators.Cache
292  def GetArchName(self):
293    return self._device.GetABI()
294
295  def GetOSName(self):
296    return 'android'
297
298  def GetDeviceTypeName(self):
299    return self._device.product_model
300
301  @decorators.Cache
302  def GetOSVersionName(self):
303    return self._device.GetProp('ro.build.id')[0]
304
305  def CanFlushIndividualFilesFromSystemCache(self):
306    return False
307
308  def FlushEntireSystemCache(self):
309    cache = cache_control.CacheControl(self._device)
310    cache.DropRamCaches()
311
312  def FlushSystemCacheForDirectory(self, directory):
313    raise NotImplementedError()
314
315  def FlushDnsCache(self):
316    self._device.RunShellCommand('ndc resolver flushdefaultif', as_root=True)
317
318  def StopApplication(self, application):
319    """Stop the given |application|.
320
321    Args:
322       application: The full package name string of the application to stop.
323    """
324    self._device.ForceStop(application)
325
326  def KillApplication(self, application):
327    """Kill the given |application|.
328
329    Might be used instead of ForceStop for efficiency reasons.
330
331    Args:
332      application: The full package name string of the application to kill.
333    """
334    assert isinstance(application, basestring)
335    self._device.KillAll(application, blocking=True, quiet=True)
336
337  def LaunchApplication(
338      self, application, parameters=None, elevate_privilege=False):
339    """Launches the given |application| with a list of |parameters| on the OS.
340
341    Args:
342      application: The full package name string of the application to launch.
343      parameters: A list of parameters to be passed to the ActivityManager.
344      elevate_privilege: Currently unimplemented on Android.
345    """
346    if elevate_privilege:
347      raise NotImplementedError("elevate_privilege isn't supported on android.")
348    if not parameters:
349      parameters = ''
350    result_lines = self._device.RunShellCommand('am start %s %s' %
351                                                (parameters, application))
352    for line in result_lines:
353      if line.startswith('Error: '):
354        raise ValueError('Failed to start "%s" with error\n  %s' %
355                         (application, line))
356
357  def IsApplicationRunning(self, application):
358    return len(self._device.GetPids(application)) > 0
359
360  def CanLaunchApplication(self, application):
361    if not self._installed_applications:
362      self._installed_applications = self._device.RunShellCommand(
363          'pm list packages')
364    return 'package:' + application in self._installed_applications
365
366  def InstallApplication(self, application):
367    self._installed_applications = None
368    self._device.Install(application)
369
370  @decorators.Cache
371  def CanCaptureVideo(self):
372    return self.GetOSVersionName() >= 'K'
373
374  def StartVideoCapture(self, min_bitrate_mbps):
375    """Starts the video capture at specified bitrate."""
376    min_bitrate_mbps = max(min_bitrate_mbps, 0.1)
377    if min_bitrate_mbps > 100:
378      raise ValueError('Android video capture cannot capture at %dmbps. '
379                       'Max capture rate is 100mbps.' % min_bitrate_mbps)
380    if self.is_video_capture_running:
381      self._video_recorder.Stop()
382    self._video_recorder = video_recorder.VideoRecorder(
383        self._device, megabits_per_second=min_bitrate_mbps)
384    self._video_recorder.Start(timeout=5)
385
386  @property
387  def is_video_capture_running(self):
388    return self._video_recorder is not None
389
390  def StopVideoCapture(self):
391    assert self.is_video_capture_running, 'Must start video capture first'
392    self._video_recorder.Stop()
393    video_file_obj = tempfile.NamedTemporaryFile()
394    self._video_recorder.Pull(video_file_obj.name)
395    self._video_recorder = None
396
397    return video.Video(video_file_obj)
398
399  def CanMonitorPower(self):
400    return self._power_monitor.CanMonitorPower()
401
402  def StartMonitoringPower(self, browser):
403    self._power_monitor.StartMonitoringPower(browser)
404
405  def StopMonitoringPower(self):
406    return self._power_monitor.StopMonitoringPower()
407
408  def CanMonitorNetworkData(self):
409    return self._device.build_version_sdk >= version_codes.LOLLIPOP
410
411  def GetNetworkData(self, browser):
412    return self._battery.GetNetworkData(browser._browser_backend.package)
413
414  def PathExists(self, device_path, timeout=None, retries=None):
415    """ Return whether the given path exists on the device.
416    This method is the same as
417    devil.android.device_utils.DeviceUtils.PathExists.
418    """
419    return self._device.PathExists(
420        device_path, timeout=timeout, retries=retries)
421
422  def GetFileContents(self, fname):
423    if not self._can_access_protected_file_contents:
424      logging.warning('%s cannot be retrieved on non-rooted device.' % fname)
425      return ''
426    return self._device.ReadFile(fname, as_root=True)
427
428  def GetPsOutput(self, columns, pid=None):
429    assert columns == ['pid', 'name'] or columns == ['pid'], \
430        'Only know how to return pid and name. Requested: ' + columns
431    command = 'ps'
432    if pid:
433      command += ' -p %d' % pid
434    ps = self._device.RunShellCommand(command, large_output=True)[1:]
435    output = []
436    for line in ps:
437      data = line.split()
438      curr_pid = data[1]
439      curr_name = data[-1]
440      if columns == ['pid', 'name']:
441        output.append([curr_pid, curr_name])
442      else:
443        output.append([curr_pid])
444    return output
445
446  def RunCommand(self, command):
447    return '\n'.join(self._device.RunShellCommand(command))
448
449  @staticmethod
450  def ParseCStateSample(sample):
451    sample_stats = {}
452    for cpu in sample:
453      values = sample[cpu].splitlines()
454      # Each state has three values after excluding the time value.
455      num_states = (len(values) - 1) / 3
456      names = values[:num_states]
457      times = values[num_states:2 * num_states]
458      cstates = {'C0': int(values[-1]) * 10 ** 6}
459      for i, state in enumerate(names):
460        if state == 'C0':
461          # The Exynos cpuidle driver for the Nexus 10 uses the name 'C0' for
462          # its WFI state.
463          # TODO(tmandel): We should verify that no other Android device
464          # actually reports time in C0 causing this to report active time as
465          # idle time.
466          state = 'WFI'
467        cstates[state] = int(times[i])
468        cstates['C0'] -= int(times[i])
469      sample_stats[cpu] = cstates
470    return sample_stats
471
472  def SetRelaxSslCheck(self, value):
473    old_flag = self._device.GetProp('socket.relaxsslcheck')
474    self._device.SetProp('socket.relaxsslcheck', value)
475    return old_flag
476
477  def ForwardHostToDevice(self, host_port, device_port):
478    self._device.adb.Forward('tcp:%d' % host_port, device_port)
479
480  def StopForwardingHost(self, host_port):
481    for line in self._device.adb.ForwardList().strip().splitlines():
482      line = line.split(' ')
483      if line[0] == self._device and line[1] == 'tcp:%s' % host_port:
484        self._device.adb.ForwardRemove('tcp:%d' % host_port)
485        break
486    else:
487      logging.warning('Port %s not found in adb forward --list for device %s',
488                      host_port, self._device)
489
490  def DismissCrashDialogIfNeeded(self):
491    """Dismiss any error dialogs.
492
493    Limit the number in case we have an error loop or we are failing to dismiss.
494    """
495    for _ in xrange(10):
496      if not self._device.DismissCrashDialogIfNeeded():
497        break
498
499  def IsAppRunning(self, process_name):
500    """Determine if the given process is running.
501
502    Args:
503      process_name: The full package name string of the process.
504    """
505    return bool(self._device.GetPids(process_name))
506
507  @property
508  def supports_test_ca(self):
509    # TODO(nednguyen): figure out how to install certificate on Android M
510    # crbug.com/593152
511    return self._device.build_version_sdk <= version_codes.LOLLIPOP_MR1
512
513  def InstallTestCa(self, ca_cert_path):
514    """Install a randomly generated root CA on the android device.
515
516    This allows transparent HTTPS testing with WPR server without need
517    to tweak application network stack.
518
519    Note: If this method fails with any exception, then RemoveTestCa will be
520    automatically called by the network_controller_backend.
521    """
522    if self._device_cert_util is not None:
523      logging.warning('Test certificate authority is already installed.')
524      return
525    self._device_cert_util = adb_install_cert.AndroidCertInstaller(
526        self._device.adb.GetDeviceSerial(), None, ca_cert_path)
527    self._device_cert_util.install_cert(overwrite_cert=True)
528
529  def RemoveTestCa(self):
530    """Remove root CA from device installed by InstallTestCa.
531
532    Note: Any exceptions raised by this method will be logged but dismissed by
533    the network_controller_backend.
534    """
535    if self._device_cert_util is not None:
536      try:
537        self._device_cert_util.remove_cert()
538      finally:
539        self._device_cert_util = None
540
541  def PushProfile(self, package, new_profile_dir):
542    """Replace application profile with files found on host machine.
543
544    Pushing the profile is slow, so we don't want to do it every time.
545    Avoid this by pushing to a safe location using PushChangedFiles, and
546    then copying into the correct location on each test run.
547
548    Args:
549      package: The full package name string of the application for which the
550        profile is to be updated.
551      new_profile_dir: Location where profile to be pushed is stored on the
552        host machine.
553    """
554    (profile_parent, profile_base) = os.path.split(new_profile_dir)
555    # If the path ends with a '/' python split will return an empty string for
556    # the base name; so we now need to get the base name from the directory.
557    if not profile_base:
558      profile_base = os.path.basename(profile_parent)
559
560    saved_profile_location = '/sdcard/profile/%s' % profile_base
561    self._device.PushChangedFiles([(new_profile_dir, saved_profile_location)])
562
563    profile_dir = self._GetProfileDir(package)
564    try:
565      self._EfficientDeviceDirectoryCopy(
566          saved_profile_location, profile_dir)
567    except Exception:
568      logging.exception('New exception caused by DeviceUtils conversion')
569      raise
570    dumpsys = self._device.RunShellCommand('dumpsys package %s' % package)
571    id_line = next(line for line in dumpsys if 'userId=' in line)
572    uid = re.search(r'\d+', id_line).group()
573    files = self._device.RunShellCommand(
574        'ls "%s"' % profile_dir, as_root=True)
575    files.remove('lib')
576    paths = ['%s%s' % (profile_dir, f) for f in files]
577    for path in paths:
578      extended_path = '%s %s/* %s/*/* %s/*/*/*' % (path, path, path, path)
579      self._device.RunShellCommand(
580          'chown %s.%s %s' % (uid, uid, extended_path))
581
582  def _EfficientDeviceDirectoryCopy(self, source, dest):
583    if not self._device_copy_script:
584      self._device.adb.Push(
585          _DEVICE_COPY_SCRIPT_FILE,
586          _DEVICE_COPY_SCRIPT_LOCATION)
587      self._device_copy_script = _DEVICE_COPY_SCRIPT_FILE
588    self._device.RunShellCommand(
589        ['sh', self._device_copy_script, source, dest])
590
591  def RemoveProfile(self, package, ignore_list):
592    """Delete application profile on device.
593
594    Args:
595      package: The full package name string of the application for which the
596        profile is to be deleted.
597      ignore_list: List of files to keep.
598    """
599    profile_dir = self._GetProfileDir(package)
600    files = self._device.RunShellCommand(
601        'ls "%s"' % profile_dir, as_root=True)
602    paths = ['"%s%s"' % (profile_dir, f) for f in files
603             if f not in ignore_list]
604    self._device.RunShellCommand('rm -r %s' % ' '.join(paths), as_root=True)
605
606  def PullProfile(self, package, output_profile_path):
607    """Copy application profile from device to host machine.
608
609    Args:
610      package: The full package name string of the application for which the
611        profile is to be copied.
612      output_profile_dir: Location where profile to be stored on host machine.
613    """
614    profile_dir = self._GetProfileDir(package)
615    logging.info("Pulling profile directory from device: '%s'->'%s'.",
616                 profile_dir, output_profile_path)
617    # To minimize bandwidth it might be good to look at whether all the data
618    # pulled down is really needed e.g. .pak files.
619    if not os.path.exists(output_profile_path):
620      os.makedirs(output_profile_path)
621    try:
622      files = self._device.RunShellCommand(['ls', profile_dir])
623    except Exception:
624      logging.exception('New exception caused by DeviceUtils conversion')
625      raise
626    for f in files:
627      # Don't pull lib, since it is created by the installer.
628      if f != 'lib':
629        source = '%s%s' % (profile_dir, f)
630        dest = os.path.join(output_profile_path, f)
631        try:
632          self._device.PullFile(source, dest, timeout=240)
633        except device_errors.CommandFailedError:
634          logging.exception('Failed to pull %s to %s', source, dest)
635
636  def _GetProfileDir(self, package):
637    """Returns the on-device location where the application profile is stored
638    based on Android convention.
639
640    Args:
641      package: The full package name string of the application.
642    """
643    return '/data/data/%s/' % package
644
645  def SetDebugApp(self, package):
646    """Set application to debugging.
647
648    Args:
649      package: The full package name string of the application.
650    """
651    if self._device.IsUserBuild():
652      logging.debug('User build device, setting debug app')
653      self._device.RunShellCommand('am set-debug-app --persistent %s' % package)
654
655  def GetLogCat(self, number_of_lines=500):
656    """Returns most recent lines of logcat dump.
657
658    Args:
659      number_of_lines: Number of lines of log to return.
660    """
661    return '\n'.join(self._device.RunShellCommand(
662        'logcat -d -t %d' % number_of_lines))
663
664  def GetStandardOutput(self):
665    return None
666
667  def GetStackTrace(self):
668    """Returns stack trace.
669
670    The stack trace consists of raw logcat dump, logcat dump with symbols,
671    and stack info from tomstone files.
672    """
673    def Decorate(title, content):
674      return "%s\n%s\n%s\n" % (title, content, '*' * 80)
675    # Get the last lines of logcat (large enough to contain stacktrace)
676    logcat = self.GetLogCat()
677    ret = Decorate('Logcat', logcat)
678    stack = os.path.join(util.GetChromiumSrcDir(), 'third_party',
679                         'android_platform', 'development', 'scripts', 'stack')
680    # Try to symbolize logcat.
681    if os.path.exists(stack):
682      cmd = [stack]
683      cmd.append('--arch=%s' % self.GetArchName())
684      p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
685      ret += Decorate('Stack from Logcat', p.communicate(input=logcat)[0])
686
687    # Try to get tombstones.
688    tombstones = os.path.join(util.GetChromiumSrcDir(), 'build', 'android',
689                              'tombstones.py')
690    if os.path.exists(tombstones):
691      ret += Decorate('Tombstones',
692                      subprocess.Popen([tombstones, '-w', '--device',
693                                        self._device.adb.GetDeviceSerial()],
694                                       stdout=subprocess.PIPE).communicate()[0])
695    return ret
696
697  def IsScreenOn(self):
698    """Determines if device screen is on."""
699    return self._device.IsScreenOn()
700
701  @staticmethod
702  def _IsScreenLocked(input_methods):
703    """Parser method for IsScreenLocked()
704
705    Args:
706      input_methods: Output from dumpsys input_methods
707
708    Returns:
709      boolean: True if screen is locked, false if screen is not locked.
710
711    Raises:
712      ValueError: An unknown value is found for the screen lock state.
713      AndroidDeviceParsingError: Error in detecting screen state.
714
715    """
716    for line in input_methods:
717      if 'mHasBeenInactive' in line:
718        for pair in line.strip().split(' '):
719          key, value = pair.split('=', 1)
720          if key == 'mHasBeenInactive':
721            if value == 'true':
722              return True
723            elif value == 'false':
724              return False
725            else:
726              raise ValueError('Unknown value for %s: %s' % (key, value))
727    raise exceptions.AndroidDeviceParsingError(str(input_methods))
728
729  def IsScreenLocked(self):
730    """Determines if device screen is locked."""
731    input_methods = self._device.RunShellCommand('dumpsys input_method',
732                                                 check_return=True)
733    return self._IsScreenLocked(input_methods)
734
735def _FixPossibleAdbInstability():
736  """Host side workaround for crbug.com/268450 (adb instability).
737
738  The adb server has a race which is mitigated by binding to a single core.
739  """
740  if not psutil:
741    return
742  for process in psutil.process_iter():
743    try:
744      if psutil.version_info >= (2, 0):
745        if 'adb' in process.name():
746          process.cpu_affinity([0])
747      else:
748        if 'adb' in process.name:
749          process.set_cpu_affinity([0])
750    except (psutil.NoSuchProcess, psutil.AccessDenied):
751      logging.warn('Failed to set adb process CPU affinity')
752