Home | History | Annotate | Download | only in chrome
      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 
      5 """Finds android browsers that can be controlled by telemetry."""
      6 
      7 import logging
      8 import os
      9 import re
     10 import subprocess
     11 import sys
     12 
     13 from telemetry import decorators
     14 from telemetry.core import browser
     15 from telemetry.core import exceptions
     16 from telemetry.core import possible_browser
     17 from telemetry.core import platform
     18 from telemetry.core import util
     19 from telemetry.core.backends import adb_commands
     20 from telemetry.core.platform import android_device
     21 from telemetry.core.backends.chrome import android_browser_backend
     22 
     23 try:
     24   import psutil  # pylint: disable=F0401
     25 except ImportError:
     26   psutil = None
     27 
     28 
     29 CHROME_PACKAGE_NAMES = {
     30   'android-content-shell':
     31       ['org.chromium.content_shell_apk',
     32        android_browser_backend.ContentShellBackendSettings,
     33        'ContentShell.apk'],
     34   'android-chrome-shell':
     35       ['org.chromium.chrome.shell',
     36        android_browser_backend.ChromeShellBackendSettings,
     37        'ChromeShell.apk'],
     38   'android-webview':
     39       ['org.chromium.telemetry_shell',
     40        android_browser_backend.WebviewBackendSettings,
     41        None],
     42   'android-webview-shell':
     43       ['org.chromium.android_webview.shell',
     44        android_browser_backend.WebviewShellBackendSettings,
     45        'AndroidWebView.apk'],
     46   'android-chrome':
     47       ['com.google.android.apps.chrome',
     48        android_browser_backend.ChromeBackendSettings,
     49        'Chrome.apk'],
     50   'android-chrome-beta':
     51       ['com.chrome.beta',
     52        android_browser_backend.ChromeBackendSettings,
     53        None],
     54   'android-chrome-dev':
     55       ['com.google.android.apps.chrome_dev',
     56        android_browser_backend.ChromeBackendSettings,
     57        None],
     58   'android-chrome-canary':
     59       ['com.chrome.canary',
     60        android_browser_backend.ChromeBackendSettings,
     61        None],
     62   'android-jb-system-chrome':
     63       ['com.android.chrome',
     64        android_browser_backend.ChromeBackendSettings,
     65        None]
     66 }
     67 
     68 
     69 class PossibleAndroidBrowser(possible_browser.PossibleBrowser):
     70   """A launchable android browser instance."""
     71   def __init__(self, browser_type, finder_options, android_platform,
     72                backend_settings, apk_name):
     73     super(PossibleAndroidBrowser, self).__init__(browser_type, 'android',
     74         finder_options, backend_settings.supports_tab_control)
     75     assert browser_type in FindAllBrowserTypes(finder_options), \
     76         ('Please add %s to android_browser_finder.FindAllBrowserTypes' %
     77          browser_type)
     78     self._platform = android_platform
     79     self._platform_backend = (
     80         android_platform._platform_backend  # pylint: disable=W0212
     81         )
     82     self._backend_settings = backend_settings
     83     self._local_apk = None
     84 
     85     chrome_root = util.GetChromiumSrcDir()
     86     if apk_name:
     87       candidate_apks = []
     88       for build_dir, build_type in util.GetBuildDirectories():
     89         apk_full_name = os.path.join(chrome_root, build_dir, build_type, 'apks',
     90                                      apk_name)
     91         if os.path.exists(apk_full_name):
     92           last_changed = os.path.getmtime(apk_full_name)
     93           candidate_apks.append((last_changed, apk_full_name))
     94 
     95       if candidate_apks:
     96         # Find the canadidate .apk with the latest modification time.
     97         newest_apk_path = sorted(candidate_apks)[-1][1]
     98         self._local_apk = newest_apk_path
     99 
    100   def __repr__(self):
    101     return 'PossibleAndroidBrowser(browser_type=%s)' % self.browser_type
    102 
    103   def _InitPlatformIfNeeded(self):
    104     pass
    105 
    106   def Create(self):
    107     self._InitPlatformIfNeeded()
    108 
    109     use_rndis_forwarder = (self.finder_options.android_rndis or
    110                            self.finder_options.browser_options.netsim or
    111                            platform.GetHostPlatform().GetOSName() != 'linux')
    112     backend = android_browser_backend.AndroidBrowserBackend(
    113         self.finder_options.browser_options, self._backend_settings,
    114         use_rndis_forwarder,
    115         output_profile_path=self.finder_options.output_profile_path,
    116         extensions_to_load=self.finder_options.extensions_to_load,
    117         target_arch=self.finder_options.target_arch,
    118         android_platform_backend=self._platform_backend)
    119     b = browser.Browser(backend,
    120                         self._platform_backend,
    121                         self._archive_path,
    122                         self._append_to_existing_wpr,
    123                         self._make_javascript_deterministic,
    124                         self._credentials_path)
    125     return b
    126 
    127   def SupportsOptions(self, finder_options):
    128     if len(finder_options.extensions_to_load) != 0:
    129       return False
    130     return True
    131 
    132   def HaveLocalAPK(self):
    133     return self._local_apk and os.path.exists(self._local_apk)
    134 
    135   @decorators.Cache
    136   def UpdateExecutableIfNeeded(self):
    137     if self.HaveLocalAPK():
    138       logging.warn('Installing %s on device if needed.' % self._local_apk)
    139       self.platform.InstallApplication(self._local_apk)
    140 
    141   def last_modification_time(self):
    142     if self.HaveLocalAPK():
    143       return os.path.getmtime(self._local_apk)
    144     return -1
    145 
    146 
    147 def SelectDefaultBrowser(possible_browsers):
    148   local_builds_by_date = sorted(possible_browsers,
    149                                 key=lambda b: b.last_modification_time())
    150 
    151   if local_builds_by_date:
    152     newest_browser = local_builds_by_date[-1]
    153     return newest_browser
    154   return None
    155 
    156 
    157 def CanFindAvailableBrowsers():
    158   if not adb_commands.IsAndroidSupported():
    159     logging.info('Android build commands unavailable on this machine. Have '
    160                  'you installed Android build dependencies?')
    161     return False
    162 
    163   try:
    164     with open(os.devnull, 'w') as devnull:
    165       proc = subprocess.Popen(
    166           ['adb', 'devices'],
    167           stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=devnull)
    168       stdout, _ = proc.communicate()
    169     if re.search(re.escape('????????????\tno permissions'), stdout) != None:
    170       logging.warn('adb devices reported a permissions error. Consider '
    171                    'restarting adb as root:')
    172       logging.warn('  adb kill-server')
    173       logging.warn('  sudo `which adb` devices\n\n')
    174     return True
    175   except OSError:
    176     platform_tools_path = os.path.join(util.GetChromiumSrcDir(),
    177         'third_party', 'android_tools', 'sdk', 'platform-tools')
    178     if (sys.platform.startswith('linux') and
    179         os.path.exists(os.path.join(platform_tools_path, 'adb'))):
    180       os.environ['PATH'] = os.pathsep.join([platform_tools_path,
    181                                             os.environ['PATH']])
    182       return True
    183   return False
    184 
    185 
    186 def FindAllBrowserTypes(_options):
    187   return CHROME_PACKAGE_NAMES.keys()
    188 
    189 
    190 def FindAllAvailableBrowsers(finder_options):
    191   """Finds all the desktop browsers available on this machine."""
    192   if not CanFindAvailableBrowsers():
    193     logging.info('No adb command found. ' +
    194                  'Will not try searching for Android browsers.')
    195     return []
    196   if finder_options.android_device:
    197     devices = [android_device.AndroidDevice(finder_options.android_device,
    198                                             finder_options.no_performance_mode)]
    199   else:
    200     devices = android_device.AndroidDevice.GetAllConnectedDevices()
    201 
    202   if len(devices) == 0:
    203     logging.info('No android devices found.')
    204     return []
    205   elif len(devices) > 1:
    206     logging.warn(
    207         'Multiple devices attached. Please specify one of the following:\n' +
    208         '\n'.join(['  --device=%s' % d.device_id for d in devices]))
    209     return []
    210 
    211   try:
    212     android_platform = platform.GetPlatformForDevice(devices[0])
    213   except exceptions.PlatformError:
    214     return []
    215 
    216   # Host side workaround for crbug.com/268450 (adb instability).
    217   # The adb server has a race which is mitigated by binding to a single core.
    218   if psutil:
    219     for proc in psutil.process_iter():
    220       try:
    221         if 'adb' in proc.name:
    222           if 'cpu_affinity' in dir(proc):
    223             proc.cpu_affinity([0])      # New versions of psutil.
    224           elif 'set_cpu_affinity' in dir(proc):
    225             proc.set_cpu_affinity([0])  # Older versions.
    226           else:
    227             logging.warn(
    228                 'Cannot set CPU affinity due to stale psutil version: %s',
    229                 '.'.join(str(x) for x in psutil.version_info))
    230       except (psutil.NoSuchProcess, psutil.AccessDenied):
    231         logging.warn('Failed to set adb process CPU affinity')
    232 
    233   possible_browsers = []
    234   for name, package_info in CHROME_PACKAGE_NAMES.iteritems():
    235     [package, backend_settings, local_apk] = package_info
    236     b = PossibleAndroidBrowser(name,
    237                                finder_options,
    238                                android_platform,
    239                                backend_settings(package),
    240                                local_apk)
    241     if b.platform.CanLaunchApplication(package) or b.HaveLocalAPK():
    242       possible_browsers.append(b)
    243   return possible_browsers
    244