| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2012 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 import json | |
| 6 import logging | |
| 7 import os | |
| 8 import subprocess | |
| 9 import sys | |
| 10 import tempfile | |
| 11 import time | |
| 12 | |
| 13 from telemetry import adb_commands | |
| 14 from telemetry import browser_backend | |
| 15 from telemetry import browser_gone_exception | |
| 16 | |
| 17 class AndroidBrowserBackend(browser_backend.BrowserBackend): | |
| 18 """The backend for controlling a browser instance running on Android. | |
| 19 """ | |
| 20 def __init__(self, options, adb, package, is_content_shell, | |
| 21 cmdline_file, activity, devtools_remote_port): | |
| 22 super(AndroidBrowserBackend, self).__init__( | |
| 23 is_content_shell=is_content_shell, | |
| 24 supports_extensions=False, options=options) | |
| 25 if len(options.extensions_to_load) > 0: | |
| 26 raise browser_backend.ExtensionsNotSupportedException( | |
| 27 'Android browser does not support extensions.') | |
| 28 # Initialize fields so that an explosion during init doesn't break in Close. | |
| 29 self._options = options | |
| 30 self._adb = adb | |
| 31 self._package = package | |
| 32 self._cmdline_file = cmdline_file | |
| 33 self._activity = activity | |
| 34 if not options.keep_test_server_ports: | |
| 35 adb_commands.ResetTestServerPortAllocation() | |
| 36 self._port = adb_commands.AllocateTestServerPort() | |
| 37 self._devtools_remote_port = devtools_remote_port | |
| 38 | |
| 39 # Kill old browser. | |
| 40 self._adb.CloseApplication(self._package) | |
| 41 self._adb.KillAll('device_forwarder') | |
| 42 self._adb.Forward('tcp:%d' % self._port, self._devtools_remote_port) | |
| 43 | |
| 44 # Chrome Android doesn't listen to --user-data-dir. | |
| 45 # TODO: symlink the app's Default, files and cache dir | |
| 46 # to somewhere safe. | |
| 47 if not is_content_shell and not options.dont_override_profile: | |
| 48 # Set up the temp dir | |
| 49 # self._tmpdir = '/sdcard/telemetry_data' | |
| 50 # self._adb.RunShellCommand('rm -r %s' % self._tmpdir) | |
| 51 # args.append('--user-data-dir=%s' % self._tmpdir) | |
| 52 pass | |
| 53 | |
| 54 # Set up the command line. | |
| 55 if is_content_shell: | |
| 56 pseudo_exec_name = 'content_shell' | |
| 57 else: | |
| 58 pseudo_exec_name = 'chrome' | |
| 59 | |
| 60 args = [pseudo_exec_name] | |
| 61 args.extend(self.GetBrowserStartupArgs()) | |
| 62 | |
| 63 with tempfile.NamedTemporaryFile() as f: | |
| 64 def EscapeIfNeeded(arg): | |
| 65 params = arg.split('=') | |
| 66 if (len(params) == 2 and | |
| 67 params[1] and params[1][0] == '"' and params[1][-1] == '"'): | |
| 68 # CommandLine.java requires this extra escaping. | |
| 69 return '%s="\\%s\\"' % (params[0], params[1]) | |
| 70 return arg.replace(' ', '" "') | |
| 71 f.write(' '.join([EscapeIfNeeded(arg) for arg in args])) | |
| 72 f.flush() | |
| 73 self._adb.Push(f.name, cmdline_file) | |
| 74 | |
| 75 # Force devtools protocol on, if not already done and we can access | |
| 76 # protected files. | |
| 77 if (not is_content_shell and | |
| 78 self._adb.Adb().CanAccessProtectedFileContents()): | |
| 79 # Make sure we can find the apps' prefs file | |
| 80 app_data_dir = '/data/data/%s' % self._package | |
| 81 prefs_file = (app_data_dir + | |
| 82 '/app_chrome/Default/Preferences') | |
| 83 if not self._adb.FileExistsOnDevice(prefs_file): | |
| 84 # Start it up the first time so we can tweak the prefs. | |
| 85 self._adb.StartActivity(self._package, | |
| 86 self._activity, | |
| 87 True, | |
| 88 None, | |
| 89 None) | |
| 90 retries = 0 | |
| 91 timeout = 3 | |
| 92 time.sleep(timeout) | |
| 93 while not self._adb.Adb().GetProtectedFileContents(prefs_file): | |
| 94 time.sleep(timeout) | |
| 95 retries += 1 | |
| 96 timeout *= 2 | |
| 97 if retries == 3: | |
| 98 logging.critical('android_browser_backend: Could not find ' | |
| 99 'preferences file %s for %s', | |
| 100 prefs_file, self._package) | |
| 101 raise browser_gone_exception.BrowserGoneException( | |
| 102 'Missing preferences file.') | |
| 103 self._adb.CloseApplication(self._package) | |
| 104 | |
| 105 preferences = json.loads(''.join( | |
| 106 self._adb.Adb().GetProtectedFileContents(prefs_file))) | |
| 107 changed = False | |
| 108 if 'devtools' not in preferences: | |
| 109 preferences['devtools'] = {} | |
| 110 changed = True | |
| 111 if not preferences['devtools'].get('remote_enabled'): | |
| 112 preferences['devtools']['remote_enabled'] = True | |
| 113 changed = True | |
| 114 if changed: | |
| 115 logging.warning('Manually enabled devtools protocol on %s' % | |
| 116 self._package) | |
| 117 txt = json.dumps(preferences, indent=2) | |
| 118 self._adb.Adb().SetProtectedFileContents(prefs_file, txt) | |
| 119 | |
| 120 # Start it up with a fresh log. | |
| 121 self._adb.RunShellCommand('logcat -c') | |
| 122 self._adb.StartActivity(self._package, | |
| 123 self._activity, | |
| 124 True, | |
| 125 None, | |
| 126 'chrome://newtab/') | |
| 127 try: | |
| 128 self._WaitForBrowserToComeUp() | |
| 129 self._PostBrowserStartupInitialization() | |
| 130 except browser_gone_exception.BrowserGoneException: | |
| 131 logging.critical('Failed to connect to browser.') | |
| 132 if not self._adb.IsRootEnabled(): | |
| 133 logging.critical( | |
| 134 'Ensure web debugging is enabled in Chrome at ' | |
| 135 '"Settings > Developer tools > Enable USB Web debugging".') | |
| 136 sys.exit(1) | |
| 137 except: | |
| 138 import traceback | |
| 139 traceback.print_exc() | |
| 140 self.Close() | |
| 141 raise | |
| 142 | |
| 143 def GetBrowserStartupArgs(self): | |
| 144 args = super(AndroidBrowserBackend, self).GetBrowserStartupArgs() | |
| 145 args.append('--disable-fre') | |
| 146 return args | |
| 147 | |
| 148 def __del__(self): | |
| 149 self.Close() | |
| 150 | |
| 151 def Close(self): | |
| 152 super(AndroidBrowserBackend, self).Close() | |
| 153 | |
| 154 self._adb.RunShellCommand('rm %s' % self._cmdline_file) | |
| 155 self._adb.CloseApplication(self._package) | |
| 156 | |
| 157 def IsBrowserRunning(self): | |
| 158 pids = self._adb.ExtractPid(self._package) | |
| 159 return len(pids) != 0 | |
| 160 | |
| 161 def GetRemotePort(self, local_port): | |
| 162 return local_port | |
| 163 | |
| 164 def GetStandardOutput(self): | |
| 165 # If we can find symbols and there is a stack, output the symbolized stack. | |
| 166 symbol_paths = [ | |
| 167 os.path.join(adb_commands.GetOutDirectory(), 'Release', 'lib.target'), | |
| 168 os.path.join(adb_commands.GetOutDirectory(), 'Debug', 'lib.target')] | |
| 169 for symbol_path in symbol_paths: | |
| 170 if not os.path.isdir(symbol_path): | |
| 171 continue | |
| 172 with tempfile.NamedTemporaryFile() as f: | |
| 173 lines = self._adb.RunShellCommand('logcat -d') | |
| 174 for line in lines: | |
| 175 f.write(line + '\n') | |
| 176 symbolized_stack = None | |
| 177 try: | |
| 178 logging.info('Symbolizing stack...') | |
| 179 symbolized_stack = subprocess.Popen([ | |
| 180 'ndk-stack', '-sym', symbol_path, | |
| 181 '-dump', f.name], stdout=subprocess.PIPE).communicate()[0] | |
| 182 except Exception: | |
| 183 pass | |
| 184 if symbolized_stack: | |
| 185 return symbolized_stack | |
| 186 # Otherwise, just return the last 100 lines of logcat. | |
| 187 return '\n'.join(self._adb.RunShellCommand('logcat -d -t 100')) | |
| 188 | |
| 189 def CreateForwarder(self, *port_pairs): | |
| 190 return adb_commands.Forwarder(self._adb, *port_pairs) | |
| OLD | NEW |