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 |