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