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.
5 """Finds android browsers that can be controlled by telemetry."""
8 import logging as real_logging
13 from telemetry.core import browser
14 from telemetry.core import possible_browser
15 from telemetry.core import util
16 from telemetry.core.backends import adb_commands
17 from telemetry.core.backends.chrome import android_browser_backend
18 from telemetry.core.platform import android_platform_backend
20 CHROME_PACKAGE_NAMES = {
21 'android-content-shell':
22 ['org.chromium.content_shell_apk',
23 android_browser_backend.ContentShellBackendSettings,
25 'android-chromium-testshell':
26 ['org.chromium.chrome.testshell',
27 android_browser_backend.ChromiumTestShellBackendSettings,
28 'ChromiumTestShell.apk'],
30 ['com.android.webview.chromium.shell',
31 android_browser_backend.WebviewBackendSettings,
34 ['com.google.android.apps.chrome',
35 android_browser_backend.ChromeBackendSettings,
37 'android-chrome-beta':
39 android_browser_backend.ChromeBackendSettings,
42 ['com.google.android.apps.chrome_dev',
43 android_browser_backend.ChromeBackendSettings,
45 'android-jb-system-chrome':
46 ['com.android.chrome',
47 android_browser_backend.ChromeBackendSettings,
51 ALL_BROWSER_TYPES = ','.join(CHROME_PACKAGE_NAMES.keys())
53 # adb shell pm list packages
55 # intents to run (pass -D url for the rest)
56 # com.android.chrome/.Main
57 # com.google.android.apps.chrome/.Main
59 class PossibleAndroidBrowser(possible_browser.PossibleBrowser):
60 """A launchable android browser instance."""
61 def __init__(self, browser_type, finder_options, backend_settings, apk_name):
62 super(PossibleAndroidBrowser, self).__init__(browser_type, finder_options)
63 self._backend_settings = backend_settings
64 self._local_apk = None
66 chrome_root = util.GetChromiumSrcDir()
69 for build_dir, build_type in util.GetBuildDirectories():
70 apk_full_name = os.path.join(chrome_root, build_dir, build_type, 'apks',
72 if os.path.exists(apk_full_name):
73 last_changed = os.path.getmtime(apk_full_name)
74 candidate_apks.append((last_changed, apk_full_name))
77 # Find the canadidate .apk with the latest modification time.
78 newest_apk_path = sorted(candidate_apks)[-1][1]
79 self._local_apk = newest_apk_path
83 return 'PossibleAndroidBrowser(browser_type=%s)' % self.browser_type
86 backend = android_browser_backend.AndroidBrowserBackend(
87 self.finder_options.browser_options, self._backend_settings,
88 self.finder_options.android_rndis,
89 output_profile_path=self.finder_options.output_profile_path,
90 extensions_to_load=self.finder_options.extensions_to_load)
91 platform_backend = android_platform_backend.AndroidPlatformBackend(
92 self._backend_settings.adb.Adb(),
93 self.finder_options.no_performance_mode)
94 b = browser.Browser(backend, platform_backend)
97 def SupportsOptions(self, finder_options):
98 if len(finder_options.extensions_to_load) != 0:
102 def HaveLocalAPK(self):
103 return self._local_apk and os.path.exists(self._local_apk)
105 def UpdateExecutableIfNeeded(self):
106 if self.HaveLocalAPK():
108 'Refreshing %s on device if needed.' % self._local_apk)
109 self._backend_settings.adb.Install(self._local_apk)
111 def last_modification_time(self):
112 if self.HaveLocalAPK():
113 return os.path.getmtime(self._local_apk)
116 def SelectDefaultBrowser(possible_browsers):
117 local_builds_by_date = sorted(possible_browsers,
118 key=lambda b: b.last_modification_time())
120 if local_builds_by_date:
121 newest_browser = local_builds_by_date[-1]
122 return newest_browser
126 def CanFindAvailableBrowsers(logging=real_logging):
127 if not adb_commands.IsAndroidSupported():
132 if adb_works == None:
134 with open(os.devnull, 'w') as devnull:
135 proc = subprocess.Popen(['adb', 'devices'],
136 stdout=subprocess.PIPE,
137 stderr=subprocess.PIPE,
139 stdout, _ = proc.communicate()
140 if re.search(re.escape('????????????\tno permissions'), stdout) != None:
142 ('adb devices reported a permissions error. Consider '
143 'restarting adb as root:'))
144 logging.warn(' adb kill-server')
145 logging.warn(' sudo `which adb` devices\n\n')
148 platform_tools_path = os.path.join(util.GetChromiumSrcDir(),
149 'third_party', 'android_tools', 'sdk', 'platform-tools')
150 if (sys.platform.startswith('linux') and
151 os.path.exists(os.path.join(platform_tools_path, 'adb'))):
152 os.environ['PATH'] = os.pathsep.join([platform_tools_path,
159 def FindAllAvailableBrowsers(finder_options, logging=real_logging):
160 """Finds all the desktop browsers available on this machine."""
161 if not CanFindAvailableBrowsers(logging=logging):
162 logging.info('No adb command found. ' +
163 'Will not try searching for Android browsers.')
167 if finder_options.android_device:
168 devices = [finder_options.android_device]
170 devices = adb_commands.GetAttachedDevices()
172 if len(devices) == 0:
173 logging.info('No android devices found.')
177 logging.warn('Multiple devices attached. ' +
178 'Please specify a device explicitly.')
183 adb = adb_commands.AdbCommands(device=device)
185 if sys.platform.startswith('linux'):
186 # Host side workaround for crbug.com/268450 (adb instability)
187 # The adb server has a race which is mitigated by binding to a single core.
188 import psutil # pylint: disable=F0401
189 pids = [p.pid for p in psutil.process_iter() if 'adb' in p.name]
190 with open(os.devnull, 'w') as devnull:
192 ret = subprocess.call(['taskset', '-p', '-c', '0', str(pid)],
193 stdout=subprocess.PIPE,
194 stderr=subprocess.PIPE,
197 logging.warn('Failed to taskset %d (%s)', pid, ret)
199 # Experimental device side workaround for crbug.com/268450 (adb instability)
200 # The /sbin/adbd process on the device appears to hang which causes adb to
201 # report that the device is offline. Our working theory is that killing
202 # the process and allowing it to be automatically relaunched will allow us
203 # to run for longer before it hangs.
204 if not finder_options.keep_test_server_ports:
205 # This would break forwarder connections, so we cannot do this if
206 # instructed to keep server ports open.
207 logging.info('Killing adbd on device')
209 logging.info('Waiting for adbd to restart')
210 adb.Adb().Adb().SendCommand('wait-for-device')
212 packages = adb.RunShellCommand('pm list packages')
213 possible_browsers = []
215 for name, package_info in CHROME_PACKAGE_NAMES.iteritems():
216 [package, backend_settings, local_apk] = package_info
217 b = PossibleAndroidBrowser(
220 backend_settings(adb, package),
223 if 'package:' + package in packages or b.HaveLocalAPK():
224 possible_browsers.append(b)
226 # See if the "forwarder" is installed -- we need this to host content locally
227 # but make it accessible to the device.
228 if (len(possible_browsers) and not finder_options.android_rndis and
229 not adb_commands.HasForwarder()):
230 logging.warn('telemetry detected an android device. However,')
231 logging.warn('Chrome\'s port-forwarder app is not available.')
232 logging.warn('Falling back to prebuilt binaries, but to build locally: ')
233 logging.warn(' ninja -C out/Release forwarder2 md5sum')
236 if not adb_commands.SetupPrebuiltTools(device):
238 return possible_browsers