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 |