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