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