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 threading 11import time 12 13from telemetry.core import android_platform 14from telemetry.core import exceptions 15from telemetry.core import util 16from telemetry import compat_mode_options 17from telemetry import decorators 18from telemetry.internal.forwarders import android_forwarder 19from telemetry.internal.platform import android_device 20from telemetry.internal.platform import linux_based_platform_backend 21from telemetry.internal.util import binary_manager 22from telemetry.internal.util import external_modules 23from telemetry.testing import test_utils 24 25from devil.android import app_ui 26from devil.android import battery_utils 27from devil.android import cpu_temperature 28from devil.android import device_errors 29from devil.android import device_utils 30from devil.android.perf import cache_control 31from devil.android.perf import perf_control 32from devil.android.perf import thermal_throttle 33from devil.android.sdk import shared_prefs 34from devil.android.tools import provision_devices 35from devil.android.tools import system_app 36from devil.android.tools import video_recorder 37 38try: 39 # devil.android.forwarder uses fcntl, which doesn't exist on Windows. 40 from devil.android import forwarder 41except ImportError: 42 forwarder = None 43 44try: 45 from devil.android.perf import surface_stats_collector 46except Exception: # pylint: disable=broad-except 47 surface_stats_collector = None 48 49psutil = external_modules.ImportOptionalModule('psutil') 50 51_ARCH_TO_STACK_TOOL_ARCH = { 52 'armeabi-v7a': 'arm', 53 'arm64-v8a': 'arm64', 54} 55_MAP_TO_USER_FRIENDLY_OS_NAMES = { 56 'l': 'lollipop', 57 'm': 'marshmallow', 58 'n': 'nougat', 59 'o': 'oreo', 60 'p': 'pie', 61 'q': '10', 62 'k': 'kitkat' 63} 64_MAP_TO_USER_FRIENDLY_DEVICE_NAMES = { 65 'gobo': 'go', 66 'W6210': 'one', 67 'AOSP on Shamu': 'nexus 6', 68 'AOSP on BullHead': 'nexus 5x' 69} 70_DEVICE_COPY_SCRIPT_FILE = os.path.abspath(os.path.join( 71 os.path.dirname(__file__), 'efficient_android_directory_copy.sh')) 72_DEVICE_COPY_SCRIPT_LOCATION = ( 73 '/data/local/tmp/efficient_android_directory_copy.sh') 74_DEVICE_MEMTRACK_HELPER_LOCATION = '/data/local/tmp/profilers/memtrack_helper' 75_DEVICE_CLEAR_SYSTEM_CACHE_TOOL_LOCATION = '/data/local/tmp/clear_system_cache' 76 77 78class _VideoRecorder(object): 79 def __init__(self): 80 self._stop_recording_signal = threading.Event() 81 self._recording_path = None 82 self._runner = None 83 84 def WaitForSignal(self): 85 self._stop_recording_signal.wait() 86 87 @property 88 def recording_path(self): 89 return self._recording_path 90 91 def Start(self, device): 92 def record_video(device, state): 93 recorder = video_recorder.VideoRecorder(device) 94 with recorder: 95 state.WaitForSignal() 96 if state.recording_path: 97 f = recorder.Pull(state.recording_path) 98 logging.info('Video written to %s' % os.path.abspath(f)) 99 100 # Start recording the video in parallel to running the story, so that the 101 # video recording here does not block running the story (which involve 102 # executing additional commands in parallel on the device). 103 parallel_devices = device_utils.DeviceUtils.parallel([device], async=True) 104 self._runner = parallel_devices.pMap(record_video, self) 105 106 def Stop(self, video_path): 107 self._recording_path = video_path 108 self._stop_recording_signal.set() 109 # Recording the video may take a few seconds in the extreme cases. So allow 110 # a few seconds when shutting down the recording. 111 self._runner.pGet(timeout=10) 112 113 114class AndroidPlatformBackend( 115 linux_based_platform_backend.LinuxBasedPlatformBackend): 116 def __init__(self, device, require_root): 117 assert device, ( 118 'AndroidPlatformBackend can only be initialized from remote device') 119 super(AndroidPlatformBackend, self).__init__(device) 120 self._device = device_utils.DeviceUtils(device.device_id) 121 self._can_elevate_privilege = False 122 self._require_root = require_root 123 if self._require_root: 124 # Trying to root the device, if possible. 125 if not self._device.HasRoot(): 126 try: 127 self._device.EnableRoot() 128 except device_errors.CommandFailedError: 129 logging.warning('Unable to root %s', str(self._device)) 130 self._can_elevate_privilege = ( 131 self._device.HasRoot() or self._device.NeedsSU()) 132 assert self._can_elevate_privilege, ( 133 'Android device must have root access to run Telemetry') 134 self._battery = battery_utils.BatteryUtils(self._device) 135 self._surface_stats_collector = None 136 self._perf_tests_setup = perf_control.PerfControl(self._device) 137 self._thermal_throttle = thermal_throttle.ThermalThrottle(self._device) 138 self._raw_display_frame_rate_measurements = [] 139 self._device_copy_script = None 140 self._system_ui = None 141 self._device_host_clock_offset = None 142 self._video_recorder = None 143 144 # TODO(https://crbug.com/1026296): Remove this once --chromium-output-dir 145 # has a default value we can use. 146 self._build_dir = util.GetUsedBuildDirectory() 147 148 _FixPossibleAdbInstability() 149 150 @property 151 def log_file_path(self): 152 return None 153 154 @classmethod 155 def SupportsDevice(cls, device): 156 return isinstance(device, android_device.AndroidDevice) 157 158 @classmethod 159 def CreatePlatformForDevice(cls, device, finder_options): 160 assert cls.SupportsDevice(device) 161 require_root = (compat_mode_options.DONT_REQUIRE_ROOTED_DEVICE not in 162 finder_options.browser_options.compatibility_mode) 163 platform_backend = AndroidPlatformBackend(device, require_root) 164 return android_platform.AndroidPlatform(platform_backend) 165 166 def _CreateForwarderFactory(self): 167 return android_forwarder.AndroidForwarderFactory(self._device) 168 169 @property 170 def device(self): 171 return self._device 172 173 def Initialize(self): 174 self.EnsureBackgroundApkInstalled() 175 176 def GetSystemUi(self): 177 if self._system_ui is None: 178 self._system_ui = app_ui.AppUi(self.device, 'com.android.systemui') 179 return self._system_ui 180 181 def GetSharedPrefs(self, package, filename, use_encrypted_path=False): 182 """Creates a Devil SharedPrefs instance. 183 184 See devil.android.sdk.shared_prefs for the documentation of the returned 185 object. 186 187 Args: 188 package: A string containing the package of the app that the SharedPrefs 189 instance will be for. 190 filename: A string containing the specific settings file of the app that 191 the SharedPrefs instance will be for. 192 use_encrypted_path: Whether to use the newer device-encrypted path 193 (/data/user_de/) instead of the older unencrypted path (/data/data/). 194 195 Returns: 196 A reference to a SharedPrefs object for the given package and filename 197 on whatever device the platform backend has a reference to. 198 """ 199 return shared_prefs.SharedPrefs( 200 self._device, package, filename, use_encrypted_path=use_encrypted_path) 201 202 def IsSvelte(self): 203 # TODO (crbug.com/973936) This function is used to find out if expectations 204 # with the Android_Svelte tag should apply to a certain build in the old 205 # expectations format. Instead of eliminating the Android_Svelte tag in the 206 # old format, we should wait until we change the expectations into the new 207 # format. And then we can get rid of this function. 208 description = self._device.GetProp('ro.build.description', cache=True) 209 return description and 'svelte' in description 210 211 def IsLowEnd(self): 212 return self.IsSvelte() or self.GetDeviceTypeName() in ('gobo', 'W6210') 213 214 def IsAosp(self): 215 description = self._device.GetProp('ro.build.description', cache=True) 216 if description is not None: 217 return 'aosp' in description 218 return False 219 220 221 def GetRemotePort(self, port): 222 return forwarder.Forwarder.DevicePortForHostPort(port) or 0 223 224 def IsRemoteDevice(self): 225 # Android device is connected via adb which is on remote. 226 return True 227 228 def IsDisplayTracingSupported(self): 229 return bool(self.GetOSVersionName() >= 'J') 230 231 def StartDisplayTracing(self): 232 assert not self._surface_stats_collector 233 # Clear any leftover data from previous timed out tests 234 self._raw_display_frame_rate_measurements = [] 235 self._surface_stats_collector = \ 236 surface_stats_collector.SurfaceStatsCollector(self._device) 237 self._surface_stats_collector.Start() 238 239 def StopDisplayTracing(self): 240 if not self._surface_stats_collector: 241 return 242 243 try: 244 refresh_period, timestamps = self._surface_stats_collector.Stop() 245 pid = self._surface_stats_collector.GetSurfaceFlingerPid() 246 finally: 247 self._surface_stats_collector = None 248 # TODO(sullivan): should this code be inline, or live elsewhere? 249 events = [_BuildEvent( 250 '__metadata', 'process_name', 'M', pid, 0, {'name': 'SurfaceFlinger'})] 251 for ts in timestamps: 252 events.append(_BuildEvent('SurfaceFlinger', 'vsync_before', 'I', pid, ts, 253 {'data': {'frame_count': 1}})) 254 255 return { 256 'traceEvents': events, 257 'metadata': { 258 'clock-domain': 'LINUX_CLOCK_MONOTONIC', 259 'surface_flinger': { 260 'refresh_period': refresh_period, 261 }, 262 } 263 } 264 265 def CanTakeScreenshot(self): 266 return True 267 268 def TakeScreenshot(self, file_path): 269 return bool(self._device.TakeScreenshot(host_path=file_path)) 270 271 def CanRecordVideo(self): 272 return True 273 274 def StartVideoRecording(self): 275 assert self._video_recorder is None 276 self._video_recorder = _VideoRecorder() 277 self._video_recorder.Start(self.device) 278 279 def StopVideoRecording(self, video_path): 280 assert self._video_recorder 281 self._video_recorder.Stop(video_path) 282 self._video_recorder = None 283 284 def CooperativelyShutdown(self, proc, app_name): 285 # Suppress the 'abstract-method' lint warning. 286 return False 287 288 def SetPerformanceMode(self, performance_mode): 289 if not self._can_elevate_privilege: 290 logging.warning('No root privileges, so ignoring performance mode.') 291 return 292 if performance_mode == android_device.KEEP_PERFORMANCE_MODE: 293 logging.info('Keeping device performance settings intact.') 294 return 295 elif performance_mode == android_device.HIGH_PERFORMANCE_MODE: 296 logging.info('Setting high performance mode.') 297 self._perf_tests_setup.SetHighPerfMode() 298 elif performance_mode == android_device.NORMAL_PERFORMANCE_MODE: 299 logging.info('Setting normal performance mode.') 300 self._perf_tests_setup.SetDefaultPerfMode() 301 elif performance_mode == android_device.LITTLE_ONLY_PERFORMANCE_MODE: 302 logging.info('Setting little-only performance mode.') 303 self._perf_tests_setup.SetLittleOnlyMode() 304 else: 305 raise ValueError('Unknown performance mode: %s' % performance_mode) 306 307 308 def CanMonitorThermalThrottling(self): 309 return True 310 311 def IsThermallyThrottled(self): 312 return self._thermal_throttle.IsThrottled() 313 314 def HasBeenThermallyThrottled(self): 315 return self._thermal_throttle.HasBeenThrottled() 316 317 def SetGraphicsMemoryTrackingEnabled(self, enabled): 318 if not enabled: 319 self.KillApplication('memtrack_helper') 320 return 321 322 binary_manager.ReinstallAndroidHelperIfNeeded( 323 'memtrack_helper', _DEVICE_MEMTRACK_HELPER_LOCATION, 324 self._device) 325 self._device.RunShellCommand( 326 [_DEVICE_MEMTRACK_HELPER_LOCATION, '-d'], as_root=True, 327 check_return=True) 328 329 def EnsureBackgroundApkInstalled(self): 330 app = 'push_apps_to_background_apk' 331 arch_name = self._device.GetABI() 332 host_path = binary_manager.FetchPath(app, 'android', arch_name) 333 if not host_path: 334 raise Exception('Error installing PushAppsToBackground.apk.') 335 self.InstallApplication(host_path) 336 337 @decorators.Cache 338 def GetArchName(self): 339 return self._device.GetABI() 340 341 def GetOSName(self): 342 return 'android' 343 344 def GetDeviceId(self): 345 return self._device.serial 346 347 def GetDeviceTypeName(self): 348 return self._device.product_model 349 350 def GetTypExpectationsTags(self): 351 # telemetry benchmark's expectations need to know the model name 352 # and if it is a low end device 353 os_version = self.GetOSVersionName().lower() 354 os_version = _MAP_TO_USER_FRIENDLY_OS_NAMES.get(os_version, os_version) 355 tags = test_utils.sanitizeTypExpectationsTags( 356 [self.GetOSName(), 'android-' + os_version]) 357 device_type_name = self.GetDeviceTypeName() 358 tags += test_utils.sanitizeTypExpectationsTags( 359 ['android-' + _MAP_TO_USER_FRIENDLY_DEVICE_NAMES.get( 360 device_type_name, device_type_name)]) 361 if self.IsLowEnd(): 362 tags.append('android-low-end') 363 tags.append('mobile') 364 return tags 365 366 @decorators.Cache 367 def GetOSVersionName(self): 368 return self._device.GetProp('ro.build.id')[0] 369 370 def GetOSVersionDetailString(self): 371 return '' # TODO(kbr): Implement this. 372 373 def GetDeviceHostClockOffset(self): 374 """Returns the difference between the device and host clocks.""" 375 if self._device_host_clock_offset is None: 376 # Get the current time in seconds since the epoch. 377 device_time = self.device.RunShellCommand( 378 ['date', '+%s'], single_line=True) 379 host_time = time.time() 380 self._device_host_clock_offset = int(int(device_time.strip()) - host_time) 381 return self._device_host_clock_offset 382 383 def CanFlushIndividualFilesFromSystemCache(self): 384 return True 385 386 def SupportFlushEntireSystemCache(self): 387 return self._can_elevate_privilege 388 389 def FlushEntireSystemCache(self): 390 cache = cache_control.CacheControl(self._device) 391 cache.DropRamCaches() 392 393 def FlushSystemCacheForDirectory(self, directory): 394 binary_manager.ReinstallAndroidHelperIfNeeded( 395 'clear_system_cache', _DEVICE_CLEAR_SYSTEM_CACHE_TOOL_LOCATION, 396 self._device) 397 self._device.RunShellCommand( 398 [_DEVICE_CLEAR_SYSTEM_CACHE_TOOL_LOCATION, '--recurse', directory], 399 as_root=True, check_return=True) 400 401 def FlushDnsCache(self): 402 self._device.RunShellCommand( 403 ['ndc', 'resolver', 'flushdefaultif'], as_root=True, check_return=True) 404 405 def StopApplication(self, application): 406 """Stop the given |application|. 407 408 Args: 409 application: The full package name string of the application to stop. 410 """ 411 self._device.ForceStop(application) 412 413 def KillApplication(self, application): 414 """Kill the given |application|. 415 416 Might be used instead of ForceStop for efficiency reasons. 417 418 Args: 419 application: The full package name string of the application to kill. 420 """ 421 assert isinstance(application, basestring) 422 self._device.KillAll(application, blocking=True, quiet=True, as_root=True) 423 424 def LaunchApplication( 425 self, application, parameters=None, elevate_privilege=False): 426 """Launches the given |application| with a list of |parameters| on the OS. 427 428 Args: 429 application: The full package name string of the application to launch. 430 parameters: A list of parameters to be passed to the ActivityManager. 431 elevate_privilege: Currently unimplemented on Android. 432 """ 433 if elevate_privilege: 434 raise NotImplementedError("elevate_privilege isn't supported on android.") 435 # TODO(catapult:#3215): Migrate to StartActivity. 436 cmd = ['am', 'start'] 437 if parameters: 438 cmd.extend(parameters) 439 cmd.append(application) 440 result_lines = self._device.RunShellCommand(cmd, check_return=True) 441 for line in result_lines: 442 if line.startswith('Error: '): 443 raise ValueError('Failed to start "%s" with error\n %s' % 444 (application, line)) 445 446 def StartActivity(self, intent, blocking): 447 """Starts an activity for the given intent on the device.""" 448 self._device.StartActivity(intent, blocking=blocking) 449 450 def CanLaunchApplication(self, application): 451 return bool(self._device.GetApplicationPaths(application)) 452 453 # pylint: disable=arguments-differ 454 def InstallApplication(self, application, modules=None): 455 self._device.Install(application, modules=modules) 456 457 def RemoveSystemPackages(self, packages): 458 system_app.RemoveSystemApps(self._device, packages) 459 460 def PathExists(self, device_path, **kwargs): 461 """ Return whether the given path exists on the device. 462 This method is the same as 463 devil.android.device_utils.DeviceUtils.PathExists. 464 """ 465 return self._device.PathExists(device_path, **kwargs) 466 467 def GetFileContents(self, fname): 468 if not self._can_elevate_privilege: 469 logging.warning('%s cannot be retrieved on non-rooted device.', fname) 470 return '' 471 return self._device.ReadFile(fname, as_root=True) 472 473 def RunCommand(self, command): 474 return '\n'.join(self._device.RunShellCommand( 475 command, shell=isinstance(command, basestring), check_return=True)) 476 477 def SetRelaxSslCheck(self, value): 478 old_flag = self._device.GetProp('socket.relaxsslcheck') 479 self._device.SetProp('socket.relaxsslcheck', value) 480 return old_flag 481 482 def DismissCrashDialogIfNeeded(self): 483 """Dismiss any error dialogs. 484 485 Limit the number in case we have an error loop or we are failing to dismiss. 486 """ 487 for _ in xrange(10): 488 if not self._device.DismissCrashDialogIfNeeded(): 489 break 490 491 def PushProfile(self, package, new_profile_dir): 492 """Replace application profile with files found on host machine. 493 494 Pushing the profile is slow, so we don't want to do it every time. 495 Avoid this by pushing to a safe location using PushChangedFiles, and 496 then copying into the correct location on each test run. 497 498 Args: 499 package: The full package name string of the application for which the 500 profile is to be updated. 501 new_profile_dir: Location where profile to be pushed is stored on the 502 host machine. 503 """ 504 (profile_parent, profile_base) = os.path.split(new_profile_dir) 505 # If the path ends with a '/' python split will return an empty string for 506 # the base name; so we now need to get the base name from the directory. 507 if not profile_base: 508 profile_base = os.path.basename(profile_parent) 509 510 provision_devices.CheckExternalStorage(self._device) 511 512 saved_profile_location = posixpath.join( 513 self._device.GetExternalStoragePath(), 514 'profile', profile_base) 515 self._device.PushChangedFiles([(new_profile_dir, saved_profile_location)], 516 delete_device_stale=True) 517 518 profile_dir = self.GetProfileDir(package) 519 self._EfficientDeviceDirectoryCopy( 520 saved_profile_location, profile_dir) 521 dumpsys = self._device.RunShellCommand( 522 ['dumpsys', 'package', package], check_return=True) 523 id_line = next(line for line in dumpsys if 'userId=' in line) 524 uid = re.search(r'\d+', id_line).group() 525 526 # Generate all of the paths copied to the device, via walking through 527 # |new_profile_dir| and doing path manipulations. This could be replaced 528 # with recursive commands (e.g. chown -R) below, but those are not well 529 # supported by older Android versions. 530 device_paths = [] 531 for root, dirs, files in os.walk(new_profile_dir): 532 rel_root = os.path.relpath(root, new_profile_dir) 533 posix_rel_root = rel_root.replace(os.sep, posixpath.sep) 534 535 device_root = posixpath.normpath(posixpath.join(profile_dir, 536 posix_rel_root)) 537 538 if rel_root == '.' and 'lib' in files: 539 files.remove('lib') 540 device_paths.extend(posixpath.join(device_root, n) for n in files + dirs) 541 542 owner_group = '%s.%s' % (uid, uid) 543 self._device.ChangeOwner(owner_group, device_paths) 544 545 # Not having the correct SELinux security context can prevent Chrome from 546 # loading files even though the mode/group/owner combination should allow 547 # it. 548 security_context = self._device.GetSecurityContextForPackage(package) 549 self._device.ChangeSecurityContext(security_context, device_paths) 550 551 def _EfficientDeviceDirectoryCopy(self, source, dest): 552 if not self._device_copy_script: 553 self._device.adb.Push( 554 _DEVICE_COPY_SCRIPT_FILE, 555 _DEVICE_COPY_SCRIPT_LOCATION) 556 self._device_copy_script = _DEVICE_COPY_SCRIPT_LOCATION 557 self._device.RunShellCommand( 558 ['sh', self._device_copy_script, source, dest], check_return=True) 559 560 def RemoveProfile(self, package, ignore_list): 561 """Delete application profile on device. 562 563 Args: 564 package: The full package name string of the application for which the 565 profile is to be deleted. 566 ignore_list: List of files to keep. 567 """ 568 profile_dir = self.GetProfileDir(package) 569 if not self._device.PathExists(profile_dir): 570 return 571 files = [ 572 posixpath.join(profile_dir, f) 573 for f in self._device.ListDirectory(profile_dir, as_root=True) 574 if f not in ignore_list] 575 if not files: 576 return 577 self._device.RemovePath(files, force=True, recursive=True, as_root=True) 578 579 def GetProfileDir(self, package): 580 """Returns the on-device location where the application profile is stored 581 based on Android convention. 582 583 Args: 584 package: The full package name string of the application. 585 """ 586 if self._require_root: 587 return '/data/data/%s/' % package 588 else: 589 return '/data/local/tmp/%s/' % package 590 591 def GetDumpLocation(self, package): 592 """Returns the location where crash dumps should be written to. 593 594 Args: 595 package: A string containing the package name of the application that the 596 dump location is for. 597 """ 598 # On Android Q+, apps by default only have access to certain directories, 599 # so use a subdirectory of the package's profile directory to guarantee that 600 # it has access to it. 601 return self.GetProfileDir(package) + 'dumps' 602 603 def SetDebugApp(self, package): 604 """Set application to debugging. 605 606 Args: 607 package: The full package name string of the application. 608 """ 609 if self._device.IsUserBuild(): 610 logging.debug('User build device, setting debug app') 611 self._device.RunShellCommand( 612 ['am', 'set-debug-app', '--persistent', package], 613 check_return=True) 614 615 def GetLogCat(self, number_of_lines=1500): 616 """Returns most recent lines of logcat dump. 617 618 Args: 619 number_of_lines: Number of lines of log to return. 620 """ 621 def decode_line(line): 622 try: 623 uline = unicode(line, encoding='utf-8') 624 return uline.encode('ascii', 'backslashreplace') 625 except Exception: # pylint: disable=broad-except 626 logging.error('Error encoding UTF-8 logcat line as ASCII.') 627 return '<MISSING LOGCAT LINE: FAILED TO ENCODE>' 628 629 logcat_output = self._device.RunShellCommand( 630 ['logcat', '-d', '-t', str(number_of_lines)], 631 check_return=True, large_output=True) 632 return '\n'.join(decode_line(l) for l in logcat_output) 633 634 def SymbolizeLogCat(self, logcat): 635 """Attempts to symbolize any crash stacks in the given logcat data. 636 637 Args: 638 logcat: A string containing the logcat data to be symbolized. 639 640 Returns: 641 A string containing the symbolized logcat data, or None if the symbolize 642 script was not found. 643 """ 644 stack = os.path.join(util.GetChromiumSrcDir(), 'third_party', 645 'android_platform', 'development', 'scripts', 'stack') 646 if _ExecutableExists(stack): 647 cmd = [stack] 648 arch = self.GetArchName() 649 arch = _ARCH_TO_STACK_TOOL_ARCH.get(arch, arch) 650 cmd.append('--arch=%s' % arch) 651 cmd.append('--output-directory=%s' % self._build_dir) 652 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 653 return p.communicate(input=logcat)[0] 654 655 return None 656 657 def GetTombstones(self): 658 """Attempts to get any tombstones currently on the device. 659 660 Returns: 661 A string containing any tombstones found on the device, or None if the 662 tombstones script was not found or failed. 663 """ 664 tombstones = os.path.join(util.GetChromiumSrcDir(), 'build', 'android', 665 'tombstones.py') 666 if _ExecutableExists(tombstones): 667 tombstones_cmd = [ 668 tombstones, '-w', 669 '--device', self._device.adb.GetDeviceSerial(), 670 '--adb-path', self._device.adb.GetAdbPath(), 671 '--output-directory=%s' % self._build_dir, 672 ] 673 try: 674 return subprocess.check_output(tombstones_cmd) 675 except subprocess.CalledProcessError: 676 return None 677 678 return None 679 680 def GetStandardOutput(self): 681 return 'Cannot get standard output on Android' 682 683 def IsScreenOn(self): 684 """Determines if device screen is on.""" 685 return self._device.IsScreenOn() 686 687 @staticmethod 688 def _ExtractLastNativeCrashPackageFromLogcat( 689 logcat, default_package_name='com.google.android.apps.chrome'): 690 # pylint: disable=line-too-long 691 # Match against lines like: 692 # <unimportant prefix> : Fatal signal 5 (SIGTRAP), code -6 in tid NNNNN (oid.apps.chrome) 693 # <a few more lines> 694 # <unimportant prefix>: Build fingerprint: 'google/bullhead/bullhead:7.1.2/N2G47F/3769476:userdebug/dev-keys' 695 # <a few more lines> 696 # <unimportant prefix> : pid: NNNNN, tid: NNNNN, name: oid.apps.chrome >>> com.google.android.apps.chrome <<< 697 # pylint: enable=line-too-long 698 fatal_signal_re = re.compile(r'.*: Fatal signal [0-9]') 699 build_fingerprint_re = re.compile(r'.*: Build fingerprint: ') 700 package_re = re.compile(r'.*: pid: [0-9]+, tid: [0-9]+, name: .*' 701 r'>>> (?P<package_name>[^ ]+) <<<') 702 last_package = default_package_name 703 build_fingerprint_found = False 704 lookahead_lines_remaining = 0 705 for line in logcat.splitlines(): 706 if fatal_signal_re.match(line): 707 lookahead_lines_remaining = 10 708 continue 709 if not lookahead_lines_remaining: 710 build_fingerprint_found = False 711 else: 712 lookahead_lines_remaining -= 1 713 if build_fingerprint_re.match(line): 714 build_fingerprint_found = True 715 continue 716 if build_fingerprint_found: 717 m = package_re.match(line) 718 if m: 719 last_package = m.group('package_name') 720 # The package name may have a trailing process name in it, 721 # for example: "org.chromium.chrome:privileged_process0". 722 last_package = last_package.split(':')[0] 723 return last_package 724 725 @staticmethod 726 def _IsScreenLocked(input_methods): 727 """Parser method for IsScreenLocked() 728 729 Args: 730 input_methods: Output from dumpsys input_methods 731 732 Returns: 733 boolean: True if screen is locked, false if screen is not locked. 734 735 Raises: 736 ValueError: An unknown value is found for the screen lock state. 737 AndroidDeviceParsingError: Error in detecting screen state. 738 739 """ 740 for line in input_methods: 741 if 'mHasBeenInactive' in line: 742 for pair in line.strip().split(' '): 743 key, value = pair.split('=', 1) 744 if key == 'mHasBeenInactive': 745 if value == 'true': 746 return True 747 elif value == 'false': 748 return False 749 else: 750 raise ValueError('Unknown value for %s: %s' % (key, value)) 751 raise exceptions.AndroidDeviceParsingError(str(input_methods)) 752 753 def IsScreenLocked(self): 754 """Determines if device screen is locked.""" 755 input_methods = self._device.RunShellCommand(['dumpsys', 'input_method'], 756 check_return=True) 757 return self._IsScreenLocked(input_methods) 758 759 def Log(self, message): 760 """Prints line to logcat.""" 761 TELEMETRY_LOGCAT_TAG = 'Telemetry' 762 self._device.RunShellCommand( 763 ['log', '-p', 'i', '-t', TELEMETRY_LOGCAT_TAG, message], 764 check_return=True) 765 766 def WaitForBatteryTemperature(self, temp): 767 # Temperature is in tenths of a degree C, so we convert to that scale. 768 self._battery.LetBatteryCoolToTemperature(temp * 10) 769 770 def WaitForCpuTemperature(self, temp): 771 controller = cpu_temperature.CpuTemperature(self._device) 772 # Check if the device temperature zones are known 773 if controller.IsSupported(): 774 controller.LetCpuCoolToTemperature(temp) 775 else: 776 logging.warn('CPU temperature cooling delay - ' 777 'CPU temperature cannot be read: Either the current ' 778 'device is not supported or the specified temperature ' 779 'zones do not exist.') 780 781 782def _FixPossibleAdbInstability(): 783 """Host side workaround for crbug.com/268450 (adb instability). 784 785 The adb server has a race which is mitigated by binding to a single core. 786 """ 787 if not psutil: 788 return 789 for process in psutil.process_iter(): 790 try: 791 if psutil.version_info >= (2, 0): 792 if 'adb' in process.name(): 793 process.cpu_affinity([0]) 794 else: 795 if 'adb' in process.name: 796 process.set_cpu_affinity([0]) 797 except (psutil.NoSuchProcess, psutil.AccessDenied): 798 logging.warn('Failed to set adb process CPU affinity') 799 800 801def _BuildEvent(cat, name, ph, pid, ts, args): 802 event = { 803 'cat': cat, 804 'name': name, 805 'ph': ph, 806 'pid': pid, 807 'tid': pid, 808 'ts': ts * 1000, 809 'args': args 810 } 811 # Instant events need to specify the scope, too. 812 if ph == 'I': 813 event['s'] = 't' 814 return event 815 816 817def _ExecutableExists(file_name): 818 return os.access(file_name, os.X_OK) 819