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