| Index: tools/telemetry/telemetry/internal/platform/android_platform_backend.py
|
| diff --git a/tools/telemetry/telemetry/internal/platform/android_platform_backend.py b/tools/telemetry/telemetry/internal/platform/android_platform_backend.py
|
| deleted file mode 100644
|
| index 86264d887713967afa7e15fc42cf3fa98f23ecda..0000000000000000000000000000000000000000
|
| --- a/tools/telemetry/telemetry/internal/platform/android_platform_backend.py
|
| +++ /dev/null
|
| @@ -1,821 +0,0 @@
|
| -# Copyright 2013 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -
|
| -import logging
|
| -import os
|
| -import re
|
| -import shutil
|
| -import subprocess
|
| -import tempfile
|
| -
|
| -from telemetry.core import android_platform
|
| -from telemetry.core import exceptions
|
| -from telemetry.core import platform
|
| -from telemetry.core import util
|
| -from telemetry import decorators
|
| -from telemetry.internal.forwarders import android_forwarder
|
| -from telemetry.internal.image_processing import video
|
| -from telemetry.internal.platform import android_device
|
| -from telemetry.internal.platform import linux_based_platform_backend
|
| -from telemetry.internal.platform.power_monitor import android_dumpsys_power_monitor
|
| -from telemetry.internal.platform.power_monitor import android_fuelgauge_power_monitor
|
| -from telemetry.internal.platform.power_monitor import android_temperature_monitor
|
| -from telemetry.internal.platform.power_monitor import monsoon_power_monitor
|
| -from telemetry.internal.platform.power_monitor import (
|
| - android_power_monitor_controller)
|
| -from telemetry.internal.platform.power_monitor import sysfs_power_monitor
|
| -from telemetry.internal.platform.profiler import android_prebuilt_profiler_helper
|
| -from telemetry.internal.util import exception_formatter
|
| -from telemetry.internal.util import external_modules
|
| -
|
| -psutil = external_modules.ImportOptionalModule('psutil')
|
| -import adb_install_cert
|
| -import certutils
|
| -import platformsettings
|
| -
|
| -from devil.android import app_ui
|
| -from devil.android import battery_utils
|
| -from devil.android import device_errors
|
| -from devil.android import device_utils
|
| -from devil.android.perf import cache_control
|
| -from devil.android.perf import perf_control
|
| -from devil.android.perf import thermal_throttle
|
| -from devil.android.sdk import version_codes
|
| -from devil.android.tools import video_recorder
|
| -
|
| -try:
|
| - from devil.android.perf import surface_stats_collector
|
| -except Exception:
|
| - surface_stats_collector = None
|
| -
|
| -
|
| -_DEVICE_COPY_SCRIPT_FILE = os.path.abspath(os.path.join(
|
| - os.path.dirname(__file__), 'efficient_android_directory_copy.sh'))
|
| -_DEVICE_COPY_SCRIPT_LOCATION = (
|
| - '/data/local/tmp/efficient_android_directory_copy.sh')
|
| -
|
| -# TODO(nednguyen): Remove this method and update the client config to point to
|
| -# the correct binary instead.
|
| -def _FindLocallyBuiltPath(binary_name):
|
| - """Finds the most recently built |binary_name|."""
|
| - command = None
|
| - command_mtime = 0
|
| - chrome_root = util.GetChromiumSrcDir()
|
| - required_mode = os.X_OK
|
| - if binary_name.endswith('.apk'):
|
| - required_mode = os.R_OK
|
| - for build_dir, build_type in util.GetBuildDirectories():
|
| - candidate = os.path.join(chrome_root, build_dir, build_type, binary_name)
|
| - if os.path.isfile(candidate) and os.access(candidate, required_mode):
|
| - candidate_mtime = os.stat(candidate).st_mtime
|
| - if candidate_mtime > command_mtime:
|
| - command = candidate
|
| - command_mtime = candidate_mtime
|
| - return command
|
| -
|
| -
|
| -def _GetBuildTypeOfPath(path):
|
| - if not path:
|
| - return None
|
| - for build_dir, build_type in util.GetBuildDirectories():
|
| - if os.path.join(build_dir, build_type) in path:
|
| - return build_type
|
| - return None
|
| -
|
| -
|
| -class AndroidPlatformBackend(
|
| - linux_based_platform_backend.LinuxBasedPlatformBackend):
|
| - def __init__(self, device, finder_options):
|
| - assert device, (
|
| - 'AndroidPlatformBackend can only be initialized from remote device')
|
| - super(AndroidPlatformBackend, self).__init__(device)
|
| - self._device = device_utils.DeviceUtils(device.device_id)
|
| - # Trying to root the device, if possible.
|
| - if not self._device.HasRoot():
|
| - try:
|
| - self._device.EnableRoot()
|
| - except device_errors.CommandFailedError:
|
| - logging.warning('Unable to root %s', str(self._device))
|
| - self._battery = battery_utils.BatteryUtils(self._device)
|
| - self._enable_performance_mode = device.enable_performance_mode
|
| - self._surface_stats_collector = None
|
| - self._perf_tests_setup = perf_control.PerfControl(self._device)
|
| - self._thermal_throttle = thermal_throttle.ThermalThrottle(self._device)
|
| - self._raw_display_frame_rate_measurements = []
|
| - try:
|
| - self._can_access_protected_file_contents = (
|
| - self._device.HasRoot() or self._device.NeedsSU())
|
| - except Exception:
|
| - logging.exception('New exception caused by DeviceUtils conversion')
|
| - raise
|
| - self._device_copy_script = None
|
| - self._power_monitor = (
|
| - android_power_monitor_controller.AndroidPowerMonitorController([
|
| - android_temperature_monitor.AndroidTemperatureMonitor(self._device),
|
| - monsoon_power_monitor.MonsoonPowerMonitor(self._device, self),
|
| - android_dumpsys_power_monitor.DumpsysPowerMonitor(
|
| - self._battery, self),
|
| - sysfs_power_monitor.SysfsPowerMonitor(self, standalone=True),
|
| - android_fuelgauge_power_monitor.FuelGaugePowerMonitor(
|
| - self._battery),
|
| - ], self._battery))
|
| - self._video_recorder = None
|
| - self._installed_applications = None
|
| -
|
| - self._wpr_ca_cert_path = None
|
| - self._device_cert_util = None
|
| - self._is_test_ca_installed = False
|
| - self._system_ui = None
|
| -
|
| - self._use_rndis_forwarder = (
|
| - finder_options.android_rndis or
|
| - finder_options.browser_options.netsim or
|
| - platform.GetHostPlatform().GetOSName() != 'linux')
|
| -
|
| - _FixPossibleAdbInstability()
|
| -
|
| - @property
|
| - def log_file_path(self):
|
| - return None
|
| -
|
| - @classmethod
|
| - def SupportsDevice(cls, device):
|
| - return isinstance(device, android_device.AndroidDevice)
|
| -
|
| - @classmethod
|
| - def CreatePlatformForDevice(cls, device, finder_options):
|
| - assert cls.SupportsDevice(device)
|
| - platform_backend = AndroidPlatformBackend(device, finder_options)
|
| - return android_platform.AndroidPlatform(platform_backend)
|
| -
|
| - @property
|
| - def forwarder_factory(self):
|
| - if not self._forwarder_factory:
|
| - self._forwarder_factory = android_forwarder.AndroidForwarderFactory(
|
| - self._device, self._use_rndis_forwarder)
|
| -
|
| - return self._forwarder_factory
|
| -
|
| - @property
|
| - def use_rndis_forwarder(self):
|
| - return self._use_rndis_forwarder
|
| -
|
| - @property
|
| - def device(self):
|
| - return self._device
|
| -
|
| - def GetSystemUi(self):
|
| - if self._system_ui is None:
|
| - self._system_ui = app_ui.AppUi(self.device, 'com.android.systemui')
|
| - return self._system_ui
|
| -
|
| - def IsSvelte(self):
|
| - try:
|
| - self._device.RunShellCommand(
|
| - 'getprop ro.build.description | grep svelte', check_return=True)
|
| - return True
|
| - except device_errors.AdbCommandFailedError:
|
| - return False
|
| -
|
| - def IsDisplayTracingSupported(self):
|
| - return bool(self.GetOSVersionName() >= 'J')
|
| -
|
| - def StartDisplayTracing(self):
|
| - assert not self._surface_stats_collector
|
| - # Clear any leftover data from previous timed out tests
|
| - self._raw_display_frame_rate_measurements = []
|
| - self._surface_stats_collector = \
|
| - surface_stats_collector.SurfaceStatsCollector(self._device)
|
| - self._surface_stats_collector.Start()
|
| -
|
| - def StopDisplayTracing(self):
|
| - if not self._surface_stats_collector:
|
| - return
|
| -
|
| - try:
|
| - refresh_period, timestamps = self._surface_stats_collector.Stop()
|
| - pid = self._surface_stats_collector.GetSurfaceFlingerPid()
|
| - finally:
|
| - self._surface_stats_collector = None
|
| - # TODO(sullivan): should this code be inline, or live elsewhere?
|
| - events = []
|
| - for ts in timestamps:
|
| - events.append({
|
| - 'cat': 'SurfaceFlinger',
|
| - 'name': 'vsync_before',
|
| - 'ts': ts,
|
| - 'pid': pid,
|
| - 'tid': pid,
|
| - 'args': {'data': {
|
| - 'frame_count': 1,
|
| - 'refresh_period': refresh_period,
|
| - }}
|
| - })
|
| - return events
|
| -
|
| - def CanTakeScreenshot(self):
|
| - return True
|
| -
|
| - def TakeScreenshot(self, file_path):
|
| - return bool(self._device.TakeScreenshot(host_path=file_path))
|
| -
|
| - def SetFullPerformanceModeEnabled(self, enabled):
|
| - if not self._enable_performance_mode:
|
| - logging.warning('CPU governor will not be set!')
|
| - return
|
| - if enabled:
|
| - self._perf_tests_setup.SetHighPerfMode()
|
| - else:
|
| - self._perf_tests_setup.SetDefaultPerfMode()
|
| -
|
| - def CanMonitorThermalThrottling(self):
|
| - return True
|
| -
|
| - def IsThermallyThrottled(self):
|
| - return self._thermal_throttle.IsThrottled()
|
| -
|
| - def HasBeenThermallyThrottled(self):
|
| - return self._thermal_throttle.HasBeenThrottled()
|
| -
|
| - def GetCpuStats(self, pid):
|
| - if not self._can_access_protected_file_contents:
|
| - logging.warning('CPU stats cannot be retrieved on non-rooted device.')
|
| - return {}
|
| - return super(AndroidPlatformBackend, self).GetCpuStats(pid)
|
| -
|
| - def GetCpuTimestamp(self):
|
| - if not self._can_access_protected_file_contents:
|
| - logging.warning('CPU timestamp cannot be retrieved on non-rooted device.')
|
| - return {}
|
| - return super(AndroidPlatformBackend, self).GetCpuTimestamp()
|
| -
|
| - def SetGraphicsMemoryTrackingEnabled(self, enabled):
|
| - if not enabled:
|
| - self.KillApplication('memtrack_helper')
|
| - return
|
| -
|
| - if not android_prebuilt_profiler_helper.InstallOnDevice(
|
| - self._device, 'memtrack_helper'):
|
| - raise Exception('Error installing memtrack_helper.')
|
| - try:
|
| - cmd = android_prebuilt_profiler_helper.GetDevicePath('memtrack_helper')
|
| - cmd += ' -d'
|
| - self._device.RunShellCommand(cmd, as_root=True, check_return=True)
|
| - except Exception:
|
| - logging.exception('New exception caused by DeviceUtils conversion')
|
| - raise
|
| -
|
| - def PurgeUnpinnedMemory(self):
|
| - """Purges the unpinned ashmem memory for the whole system.
|
| -
|
| - This can be used to make memory measurements more stable. Requires root.
|
| - """
|
| - if not self._can_access_protected_file_contents:
|
| - logging.warning('Cannot run purge_ashmem. Requires a rooted device.')
|
| - return
|
| -
|
| - if not android_prebuilt_profiler_helper.InstallOnDevice(
|
| - self._device, 'purge_ashmem'):
|
| - raise Exception('Error installing purge_ashmem.')
|
| - try:
|
| - output = self._device.RunShellCommand(
|
| - android_prebuilt_profiler_helper.GetDevicePath('purge_ashmem'))
|
| - except Exception:
|
| - logging.exception('New exception caused by DeviceUtils conversion')
|
| - raise
|
| - for l in output:
|
| - logging.info(l)
|
| -
|
| - def GetMemoryStats(self, pid):
|
| - memory_usage = self._device.GetMemoryUsageForPid(pid)
|
| - if not memory_usage:
|
| - return {}
|
| - return {'ProportionalSetSize': memory_usage['Pss'] * 1024,
|
| - 'SharedDirty': memory_usage['Shared_Dirty'] * 1024,
|
| - 'PrivateDirty': memory_usage['Private_Dirty'] * 1024,
|
| - 'VMPeak': memory_usage['VmHWM'] * 1024}
|
| -
|
| - def GetChildPids(self, pid):
|
| - child_pids = []
|
| - ps = self.GetPsOutput(['pid', 'name'])
|
| - for curr_pid, curr_name in ps:
|
| - if int(curr_pid) == pid:
|
| - name = curr_name
|
| - for curr_pid, curr_name in ps:
|
| - if curr_name.startswith(name) and curr_name != name:
|
| - child_pids.append(int(curr_pid))
|
| - break
|
| - return child_pids
|
| -
|
| - @decorators.Cache
|
| - def GetCommandLine(self, pid):
|
| - ps = self.GetPsOutput(['pid', 'name'], pid)
|
| - if not ps:
|
| - raise exceptions.ProcessGoneException()
|
| - return ps[0][1]
|
| -
|
| - @decorators.Cache
|
| - def GetArchName(self):
|
| - return self._device.GetABI()
|
| -
|
| - def GetOSName(self):
|
| - return 'android'
|
| -
|
| - def GetDeviceTypeName(self):
|
| - return self._device.product_model
|
| -
|
| - @decorators.Cache
|
| - def GetOSVersionName(self):
|
| - return self._device.GetProp('ro.build.id')[0]
|
| -
|
| - def CanFlushIndividualFilesFromSystemCache(self):
|
| - return False
|
| -
|
| - def FlushEntireSystemCache(self):
|
| - cache = cache_control.CacheControl(self._device)
|
| - cache.DropRamCaches()
|
| -
|
| - def FlushSystemCacheForDirectory(self, directory):
|
| - raise NotImplementedError()
|
| -
|
| - def FlushDnsCache(self):
|
| - self._device.RunShellCommand('ndc resolver flushdefaultif', as_root=True)
|
| -
|
| - def StopApplication(self, application):
|
| - """Stop the given |application|.
|
| -
|
| - Args:
|
| - application: The full package name string of the application to stop.
|
| - """
|
| - self._device.ForceStop(application)
|
| -
|
| - def KillApplication(self, application):
|
| - """Kill the given |application|.
|
| -
|
| - Might be used instead of ForceStop for efficiency reasons.
|
| -
|
| - Args:
|
| - application: The full package name string of the application to kill.
|
| - """
|
| - self._device.KillAll(application, blocking=True, quiet=True)
|
| -
|
| - def LaunchApplication(
|
| - self, application, parameters=None, elevate_privilege=False):
|
| - """Launches the given |application| with a list of |parameters| on the OS.
|
| -
|
| - Args:
|
| - application: The full package name string of the application to launch.
|
| - parameters: A list of parameters to be passed to the ActivityManager.
|
| - elevate_privilege: Currently unimplemented on Android.
|
| - """
|
| - if elevate_privilege:
|
| - raise NotImplementedError("elevate_privilege isn't supported on android.")
|
| - if not parameters:
|
| - parameters = ''
|
| - result_lines = self._device.RunShellCommand('am start %s %s' %
|
| - (parameters, application))
|
| - for line in result_lines:
|
| - if line.startswith('Error: '):
|
| - raise ValueError('Failed to start "%s" with error\n %s' %
|
| - (application, line))
|
| -
|
| - def IsApplicationRunning(self, application):
|
| - return len(self._device.GetPids(application)) > 0
|
| -
|
| - def CanLaunchApplication(self, application):
|
| - if not self._installed_applications:
|
| - self._installed_applications = self._device.RunShellCommand(
|
| - 'pm list packages')
|
| - return 'package:' + application in self._installed_applications
|
| -
|
| - def InstallApplication(self, application):
|
| - self._installed_applications = None
|
| - self._device.Install(application)
|
| -
|
| - @decorators.Cache
|
| - def CanCaptureVideo(self):
|
| - return self.GetOSVersionName() >= 'K'
|
| -
|
| - def StartVideoCapture(self, min_bitrate_mbps):
|
| - """Starts the video capture at specified bitrate."""
|
| - min_bitrate_mbps = max(min_bitrate_mbps, 0.1)
|
| - if min_bitrate_mbps > 100:
|
| - raise ValueError('Android video capture cannot capture at %dmbps. '
|
| - 'Max capture rate is 100mbps.' % min_bitrate_mbps)
|
| - if self.is_video_capture_running:
|
| - self._video_recorder.Stop()
|
| - self._video_recorder = video_recorder.VideoRecorder(
|
| - self._device, megabits_per_second=min_bitrate_mbps)
|
| - self._video_recorder.Start(timeout=5)
|
| -
|
| - @property
|
| - def is_video_capture_running(self):
|
| - return self._video_recorder is not None
|
| -
|
| - def StopVideoCapture(self):
|
| - assert self.is_video_capture_running, 'Must start video capture first'
|
| - self._video_recorder.Stop()
|
| - video_file_obj = tempfile.NamedTemporaryFile()
|
| - self._video_recorder.Pull(video_file_obj.name)
|
| - self._video_recorder = None
|
| -
|
| - return video.Video(video_file_obj)
|
| -
|
| - def CanMonitorPower(self):
|
| - return self._power_monitor.CanMonitorPower()
|
| -
|
| - def StartMonitoringPower(self, browser):
|
| - self._power_monitor.StartMonitoringPower(browser)
|
| -
|
| - def StopMonitoringPower(self):
|
| - return self._power_monitor.StopMonitoringPower()
|
| -
|
| - def CanMonitorNetworkData(self):
|
| - return self._device.build_version_sdk >= version_codes.LOLLIPOP
|
| -
|
| - def GetNetworkData(self, browser):
|
| - return self._battery.GetNetworkData(browser._browser_backend.package)
|
| -
|
| - def PathExists(self, device_path, timeout=None, retries=None):
|
| - """ Return whether the given path exists on the device.
|
| - This method is the same as
|
| - devil.android.device_utils.DeviceUtils.PathExists.
|
| - """
|
| - return self._device.PathExists(
|
| - device_path, timeout=timeout, retries=retries)
|
| -
|
| - def GetFileContents(self, fname):
|
| - if not self._can_access_protected_file_contents:
|
| - logging.warning('%s cannot be retrieved on non-rooted device.' % fname)
|
| - return ''
|
| - return self._device.ReadFile(fname, as_root=True)
|
| -
|
| - def GetPsOutput(self, columns, pid=None):
|
| - assert columns == ['pid', 'name'] or columns == ['pid'], \
|
| - 'Only know how to return pid and name. Requested: ' + columns
|
| - command = 'ps'
|
| - if pid:
|
| - command += ' -p %d' % pid
|
| - ps = self._device.RunShellCommand(command, large_output=True)[1:]
|
| - output = []
|
| - for line in ps:
|
| - data = line.split()
|
| - curr_pid = data[1]
|
| - curr_name = data[-1]
|
| - if columns == ['pid', 'name']:
|
| - output.append([curr_pid, curr_name])
|
| - else:
|
| - output.append([curr_pid])
|
| - return output
|
| -
|
| - def RunCommand(self, command):
|
| - return '\n'.join(self._device.RunShellCommand(command))
|
| -
|
| - @staticmethod
|
| - def ParseCStateSample(sample):
|
| - sample_stats = {}
|
| - for cpu in sample:
|
| - values = sample[cpu].splitlines()
|
| - # Each state has three values after excluding the time value.
|
| - num_states = (len(values) - 1) / 3
|
| - names = values[:num_states]
|
| - times = values[num_states:2 * num_states]
|
| - cstates = {'C0': int(values[-1]) * 10 ** 6}
|
| - for i, state in enumerate(names):
|
| - if state == 'C0':
|
| - # The Exynos cpuidle driver for the Nexus 10 uses the name 'C0' for
|
| - # its WFI state.
|
| - # TODO(tmandel): We should verify that no other Android device
|
| - # actually reports time in C0 causing this to report active time as
|
| - # idle time.
|
| - state = 'WFI'
|
| - cstates[state] = int(times[i])
|
| - cstates['C0'] -= int(times[i])
|
| - sample_stats[cpu] = cstates
|
| - return sample_stats
|
| -
|
| - def SetRelaxSslCheck(self, value):
|
| - old_flag = self._device.GetProp('socket.relaxsslcheck')
|
| - self._device.SetProp('socket.relaxsslcheck', value)
|
| - return old_flag
|
| -
|
| - def ForwardHostToDevice(self, host_port, device_port):
|
| - self._device.adb.Forward('tcp:%d' % host_port, device_port)
|
| -
|
| - def StopForwardingHost(self, host_port):
|
| - for line in self._device.adb.ForwardList().strip().splitlines():
|
| - line = line.split(' ')
|
| - if line[0] == self._device and line[1] == 'tcp:%s' % host_port:
|
| - self._device.adb.ForwardRemove('tcp:%d' % host_port)
|
| - break
|
| - else:
|
| - logging.warning('Port %s not found in adb forward --list for device %s',
|
| - host_port, self._device)
|
| -
|
| - def DismissCrashDialogIfNeeded(self):
|
| - """Dismiss any error dialogs.
|
| -
|
| - Limit the number in case we have an error loop or we are failing to dismiss.
|
| - """
|
| - for _ in xrange(10):
|
| - if not self._device.DismissCrashDialogIfNeeded():
|
| - break
|
| -
|
| - def IsAppRunning(self, process_name):
|
| - """Determine if the given process is running.
|
| -
|
| - Args:
|
| - process_name: The full package name string of the process.
|
| - """
|
| - return bool(self._device.GetPids(process_name))
|
| -
|
| - @property
|
| - def wpr_ca_cert_path(self):
|
| - """Path to root certificate installed on browser (or None).
|
| -
|
| - If this is set, web page replay will use it to sign HTTPS responses.
|
| - """
|
| - if self._wpr_ca_cert_path:
|
| - assert os.path.isfile(self._wpr_ca_cert_path)
|
| - return self._wpr_ca_cert_path
|
| -
|
| - def InstallTestCa(self):
|
| - """Install a randomly generated root CA on the android device.
|
| -
|
| - This allows transparent HTTPS testing with WPR server without need
|
| - to tweak application network stack.
|
| - """
|
| - # TODO(slamm): Move certificate creation related to webpagereplay.py.
|
| - # The only code that needs to be in platform backend is installing the cert.
|
| - if certutils.openssl_import_error:
|
| - logging.warning(
|
| - 'The OpenSSL module is unavailable. '
|
| - 'Will fallback to ignoring certificate errors.')
|
| - return
|
| - if not platformsettings.HasSniSupport():
|
| - logging.warning(
|
| - 'Web Page Replay requires SNI support (pyOpenSSL 0.13 or greater) '
|
| - 'to generate certificates from a test CA. '
|
| - 'Will fallback to ignoring certificate errors.')
|
| - return
|
| - try:
|
| - self._wpr_ca_cert_path = os.path.join(tempfile.mkdtemp(), 'testca.pem')
|
| - certutils.write_dummy_ca_cert(*certutils.generate_dummy_ca_cert(),
|
| - cert_path=self._wpr_ca_cert_path)
|
| - self._device_cert_util = adb_install_cert.AndroidCertInstaller(
|
| - self._device.adb.GetDeviceSerial(), None, self._wpr_ca_cert_path)
|
| - logging.info('Installing test certificate authority on device: %s',
|
| - str(self._device))
|
| - self._device_cert_util.install_cert(overwrite_cert=True)
|
| - self._is_test_ca_installed = True
|
| - except Exception as e:
|
| - # Fallback to ignoring certificate errors.
|
| - self.RemoveTestCa()
|
| - logging.warning(
|
| - 'Unable to install test certificate authority on device: %s. '
|
| - 'Will fallback to ignoring certificate errors. Install error: %s',
|
| - str(self._device), e)
|
| -
|
| - @property
|
| - def is_test_ca_installed(self):
|
| - return self._is_test_ca_installed
|
| -
|
| - def RemoveTestCa(self):
|
| - """Remove root CA generated by previous call to InstallTestCa().
|
| -
|
| - Removes the test root certificate from both the device and host machine.
|
| - """
|
| - if not self._wpr_ca_cert_path:
|
| - return
|
| -
|
| - if self._is_test_ca_installed:
|
| - try:
|
| - self._device_cert_util.remove_cert()
|
| - except Exception:
|
| - # Best effort cleanup - show the error and continue.
|
| - exception_formatter.PrintFormattedException(
|
| - msg=('Error while trying to remove certificate authority: %s. '
|
| - % str(self._device)))
|
| - self._is_test_ca_installed = False
|
| -
|
| - shutil.rmtree(os.path.dirname(self._wpr_ca_cert_path), ignore_errors=True)
|
| - self._wpr_ca_cert_path = None
|
| - self._device_cert_util = None
|
| -
|
| - def PushProfile(self, package, new_profile_dir):
|
| - """Replace application profile with files found on host machine.
|
| -
|
| - Pushing the profile is slow, so we don't want to do it every time.
|
| - Avoid this by pushing to a safe location using PushChangedFiles, and
|
| - then copying into the correct location on each test run.
|
| -
|
| - Args:
|
| - package: The full package name string of the application for which the
|
| - profile is to be updated.
|
| - new_profile_dir: Location where profile to be pushed is stored on the
|
| - host machine.
|
| - """
|
| - (profile_parent, profile_base) = os.path.split(new_profile_dir)
|
| - # If the path ends with a '/' python split will return an empty string for
|
| - # the base name; so we now need to get the base name from the directory.
|
| - if not profile_base:
|
| - profile_base = os.path.basename(profile_parent)
|
| -
|
| - saved_profile_location = '/sdcard/profile/%s' % profile_base
|
| - self._device.PushChangedFiles([(new_profile_dir, saved_profile_location)])
|
| -
|
| - profile_dir = self._GetProfileDir(package)
|
| - try:
|
| - self._EfficientDeviceDirectoryCopy(
|
| - saved_profile_location, profile_dir)
|
| - except Exception:
|
| - logging.exception('New exception caused by DeviceUtils conversion')
|
| - raise
|
| - dumpsys = self._device.RunShellCommand('dumpsys package %s' % package)
|
| - id_line = next(line for line in dumpsys if 'userId=' in line)
|
| - uid = re.search(r'\d+', id_line).group()
|
| - files = self._device.RunShellCommand(
|
| - 'ls "%s"' % profile_dir, as_root=True)
|
| - files.remove('lib')
|
| - paths = ['%s%s' % (profile_dir, f) for f in files]
|
| - for path in paths:
|
| - extended_path = '%s %s/* %s/*/* %s/*/*/*' % (path, path, path, path)
|
| - self._device.RunShellCommand(
|
| - 'chown %s.%s %s' % (uid, uid, extended_path))
|
| -
|
| - def _EfficientDeviceDirectoryCopy(self, source, dest):
|
| - if not self._device_copy_script:
|
| - self._device.adb.Push(
|
| - _DEVICE_COPY_SCRIPT_FILE,
|
| - _DEVICE_COPY_SCRIPT_LOCATION)
|
| - self._device_copy_script = _DEVICE_COPY_SCRIPT_FILE
|
| - self._device.RunShellCommand(
|
| - ['sh', self._device_copy_script, source, dest])
|
| -
|
| - def RemoveProfile(self, package, ignore_list):
|
| - """Delete application profile on device.
|
| -
|
| - Args:
|
| - package: The full package name string of the application for which the
|
| - profile is to be deleted.
|
| - ignore_list: List of files to keep.
|
| - """
|
| - profile_dir = self._GetProfileDir(package)
|
| - files = self._device.RunShellCommand(
|
| - 'ls "%s"' % profile_dir, as_root=True)
|
| - paths = ['"%s%s"' % (profile_dir, f) for f in files
|
| - if f not in ignore_list]
|
| - self._device.RunShellCommand('rm -r %s' % ' '.join(paths), as_root=True)
|
| -
|
| - def PullProfile(self, package, output_profile_path):
|
| - """Copy application profile from device to host machine.
|
| -
|
| - Args:
|
| - package: The full package name string of the application for which the
|
| - profile is to be copied.
|
| - output_profile_dir: Location where profile to be stored on host machine.
|
| - """
|
| - profile_dir = self._GetProfileDir(package)
|
| - logging.info("Pulling profile directory from device: '%s'->'%s'.",
|
| - profile_dir, output_profile_path)
|
| - # To minimize bandwidth it might be good to look at whether all the data
|
| - # pulled down is really needed e.g. .pak files.
|
| - if not os.path.exists(output_profile_path):
|
| - os.makedirs(output_profile_path)
|
| - try:
|
| - files = self._device.RunShellCommand(['ls', profile_dir])
|
| - except Exception:
|
| - logging.exception('New exception caused by DeviceUtils conversion')
|
| - raise
|
| - for f in files:
|
| - # Don't pull lib, since it is created by the installer.
|
| - if f != 'lib':
|
| - source = '%s%s' % (profile_dir, f)
|
| - dest = os.path.join(output_profile_path, f)
|
| - try:
|
| - self._device.PullFile(source, dest, timeout=240)
|
| - except device_errors.CommandFailedError:
|
| - logging.exception('Failed to pull %s to %s', source, dest)
|
| -
|
| - def _GetProfileDir(self, package):
|
| - """Returns the on-device location where the application profile is stored
|
| - based on Android convention.
|
| -
|
| - Args:
|
| - package: The full package name string of the application.
|
| - """
|
| - return '/data/data/%s/' % package
|
| -
|
| - def SetDebugApp(self, package):
|
| - """Set application to debugging.
|
| -
|
| - Args:
|
| - package: The full package name string of the application.
|
| - """
|
| - if self._device.IsUserBuild():
|
| - logging.debug('User build device, setting debug app')
|
| - self._device.RunShellCommand('am set-debug-app --persistent %s' % package)
|
| -
|
| - def GetLogCat(self, number_of_lines=500):
|
| - """Returns most recent lines of logcat dump.
|
| -
|
| - Args:
|
| - number_of_lines: Number of lines of log to return.
|
| - """
|
| - return '\n'.join(self._device.RunShellCommand(
|
| - 'logcat -d -t %d' % number_of_lines))
|
| -
|
| - def GetStandardOutput(self):
|
| - return None
|
| -
|
| - def GetStackTrace(self, target_arch):
|
| - """Returns stack trace.
|
| -
|
| - The stack trace consists of raw logcat dump, logcat dump with symbols,
|
| - and stack info from tomstone files.
|
| -
|
| - Args:
|
| - target_arch: String specifying device architecture (eg. arm, arm64, mips,
|
| - x86, x86_64)
|
| - """
|
| - def Decorate(title, content):
|
| - return "%s\n%s\n%s\n" % (title, content, '*' * 80)
|
| - # Get the last lines of logcat (large enough to contain stacktrace)
|
| - logcat = self.GetLogCat()
|
| - ret = Decorate('Logcat', logcat)
|
| - stack = os.path.join(util.GetChromiumSrcDir(), 'third_party',
|
| - 'android_platform', 'development', 'scripts', 'stack')
|
| - # Try to symbolize logcat.
|
| - if os.path.exists(stack):
|
| - cmd = [stack]
|
| - if target_arch:
|
| - cmd.append('--arch=%s' % target_arch)
|
| - p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
| - ret += Decorate('Stack from Logcat', p.communicate(input=logcat)[0])
|
| -
|
| - # Try to get tombstones.
|
| - tombstones = os.path.join(util.GetChromiumSrcDir(), 'build', 'android',
|
| - 'tombstones.py')
|
| - if os.path.exists(tombstones):
|
| - ret += Decorate('Tombstones',
|
| - subprocess.Popen([tombstones, '-w', '--device',
|
| - self._device.adb.GetDeviceSerial()],
|
| - stdout=subprocess.PIPE).communicate()[0])
|
| - return ret
|
| -
|
| - def IsScreenOn(self):
|
| - """Determines if device screen is on."""
|
| - return self._device.IsScreenOn()
|
| -
|
| - @staticmethod
|
| - def _IsScreenLocked(input_methods):
|
| - """Parser method for IsScreenLocked()
|
| -
|
| - Args:
|
| - input_methods: Output from dumpsys input_methods
|
| -
|
| - Returns:
|
| - boolean: True if screen is locked, false if screen is not locked.
|
| -
|
| - Raises:
|
| - ValueError: An unknown value is found for the screen lock state.
|
| - AndroidDeviceParsingError: Error in detecting screen state.
|
| -
|
| - """
|
| - for line in input_methods:
|
| - if 'mHasBeenInactive' in line:
|
| - for pair in line.strip().split(' '):
|
| - key, value = pair.split('=', 1)
|
| - if key == 'mHasBeenInactive':
|
| - if value == 'true':
|
| - return True
|
| - elif value == 'false':
|
| - return False
|
| - else:
|
| - raise ValueError('Unknown value for %s: %s' % (key, value))
|
| - raise exceptions.AndroidDeviceParsingError(str(input_methods))
|
| -
|
| - def IsScreenLocked(self):
|
| - """Determines if device screen is locked."""
|
| - input_methods = self._device.RunShellCommand('dumpsys input_method',
|
| - check_return=True)
|
| - return self._IsScreenLocked(input_methods)
|
| -
|
| -def _FixPossibleAdbInstability():
|
| - """Host side workaround for crbug.com/268450 (adb instability).
|
| -
|
| - The adb server has a race which is mitigated by binding to a single core.
|
| - """
|
| - if not psutil:
|
| - return
|
| - for process in psutil.process_iter():
|
| - try:
|
| - if psutil.version_info >= (2, 0):
|
| - if 'adb' in process.name():
|
| - process.cpu_affinity([0])
|
| - else:
|
| - if 'adb' in process.name:
|
| - process.set_cpu_affinity([0])
|
| - except (psutil.NoSuchProcess, psutil.AccessDenied):
|
| - logging.warn('Failed to set adb process CPU affinity')
|
|
|