OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright 2014 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 import atexit | |
7 import logging | |
8 import os | |
9 import os.path | |
10 import subprocess | |
11 import sys | |
12 import threading | |
13 import time | |
14 | |
15 import SimpleHTTPServer | |
16 import SocketServer | |
17 | |
18 from mopy.config import Config | |
19 from mopy.paths import Paths | |
20 | |
21 sys.path.insert(0, os.path.join(Paths().src_root, 'build', 'android')) | |
22 from pylib import android_commands | |
23 from pylib import constants | |
24 from pylib import forwarder | |
25 | |
26 | |
27 # Tags used by the mojo shell application logs. | |
28 LOGCAT_TAGS = [ | |
29 'AndroidHandler', | |
30 'MojoFileHelper', | |
31 'MojoMain', | |
32 'MojoShellActivity', | |
33 'MojoShellApplication', | |
34 'chromium', | |
35 ] | |
36 | |
37 MOJO_SHELL_PACKAGE_NAME = 'org.chromium.mojo.shell' | |
38 | |
39 | |
40 class Context(object): | |
41 """ | |
42 The context object allowing to run multiple runs of the shell. | |
43 """ | |
44 def __init__(self, device, device_port): | |
45 self.device = device | |
46 self.device_port = device_port | |
47 | |
48 | |
49 class _SilentTCPServer(SocketServer.TCPServer): | |
50 """ | |
51 A TCPServer that won't display any error, unless debugging is enabled. This is | |
52 useful because the client might stop while it is fetching an URL, which causes | |
53 spurious error messages. | |
54 """ | |
55 def handle_error(self, request, client_address): | |
56 """ | |
57 Override the base class method to have conditional logging. | |
58 """ | |
59 if logging.getLogger().isEnabledFor(logging.DEBUG): | |
60 super(_SilentTCPServer, self).handle_error(request, client_address) | |
61 | |
62 | |
63 def _GetHandlerClassForPath(base_path): | |
64 class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): | |
65 """ | |
66 Handler for SocketServer.TCPServer that will serve the files from | |
67 |base_path| directory over http. | |
68 """ | |
69 | |
70 def translate_path(self, path): | |
71 path_from_current = ( | |
72 SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path)) | |
73 return os.path.join(base_path, os.path.relpath(path_from_current)) | |
74 | |
75 def log_message(self, *_): | |
76 """ | |
77 Override the base class method to disable logging. | |
78 """ | |
79 pass | |
80 | |
81 return RequestHandler | |
82 | |
83 | |
84 def _ExitIfNeeded(process): | |
85 """ | |
86 Exits |process| if it is still alive. | |
87 """ | |
88 if process.poll() is None: | |
89 process.kill() | |
90 | |
91 | |
92 def _ReadFifo(context, fifo_path, pipe, on_fifo_closed): | |
93 """ | |
94 Reads |fifo_path| on the device and write the contents to |pipe|. Calls | |
95 |on_fifo_closed| when the fifo is closed. This method will block until | |
96 |fifo_path| exists. | |
97 """ | |
98 def Run(): | |
99 while not context.device.FileExistsOnDevice(fifo_path): | |
100 time.sleep(1) | |
101 stdout_cat = subprocess.Popen([constants.GetAdbPath(), | |
102 'shell', | |
103 'cat', | |
104 fifo_path], | |
105 stdout=pipe) | |
106 atexit.register(_ExitIfNeeded, stdout_cat) | |
107 stdout_cat.wait() | |
108 if on_fifo_closed: | |
109 on_fifo_closed() | |
110 | |
111 thread = threading.Thread(target=Run, name="StdoutRedirector") | |
112 thread.start() | |
113 | |
114 | |
115 def PrepareShellRun(config): | |
116 """ | |
117 Returns a context allowing a shell to be run. | |
118 | |
119 This will start an internal http server to serve mojo applications, forward a | |
120 local port on the device to this http server and install the current version | |
121 of the mojo shell. | |
122 """ | |
123 build_dir = Paths(config).build_dir | |
124 constants.SetOutputDirectort(build_dir) | |
125 | |
126 httpd = _SilentTCPServer(('127.0.0.1', 0), _GetHandlerClassForPath(build_dir)) | |
127 atexit.register(httpd.shutdown) | |
128 host_port = httpd.server_address[1] | |
129 | |
130 http_thread = threading.Thread(target=httpd.serve_forever) | |
131 http_thread.daemon = True | |
132 http_thread.start() | |
133 | |
134 device = android_commands.AndroidCommands( | |
135 android_commands.GetAttachedDevices()[0]) | |
136 device.EnableAdbRoot() | |
137 | |
138 device.ManagedInstall(os.path.join(build_dir, 'apks', 'MojoShell.apk'), | |
139 keep_data=True, | |
140 package_name=MOJO_SHELL_PACKAGE_NAME) | |
141 | |
142 atexit.register(forwarder.Forwarder.UnmapAllDevicePorts, device) | |
143 forwarder.Forwarder.Map([(0, host_port)], device) | |
144 context = Context(device, | |
145 forwarder.Forwarder.DevicePortForHostPort(host_port)) | |
146 | |
147 atexit.register(StopShell, context) | |
148 return context | |
149 | |
150 | |
151 def StartShell(context, arguments, stdout=None, on_application_stop=None): | |
152 """ | |
153 Starts the mojo shell, passing it the given arguments. | |
154 | |
155 If stdout is not None, it should be a valid argument for subprocess.Popen. | |
156 """ | |
157 STDOUT_PIPE = "/data/data/%s/stdout.fifo" % MOJO_SHELL_PACKAGE_NAME | |
158 | |
159 cmd = ('am start' | |
160 ' -W' | |
161 ' -S' | |
162 ' -a android.intent.action.VIEW' | |
163 ' -n %s/.MojoShellActivity' % MOJO_SHELL_PACKAGE_NAME) | |
164 | |
165 parameters = ['--origin=http://127.0.0.1:%d/' % context.device_port] | |
166 if stdout or on_application_stop: | |
167 context.device.RunShellCommand('rm %s' % STDOUT_PIPE) | |
168 parameters.append('--fifo-path=%s' % STDOUT_PIPE) | |
169 _ReadFifo(context, STDOUT_PIPE, stdout, on_application_stop) | |
170 parameters += arguments | |
171 cmd += ' --esa parameters \"%s\"' % ','.join(parameters) | |
172 | |
173 context.device.RunShellCommand(cmd) | |
174 | |
175 | |
176 def StopShell(context): | |
177 """ | |
178 Stops the mojo shell. | |
179 """ | |
180 context.device.RunShellCommand('am force-stop %s' % MOJO_SHELL_PACKAGE_NAME) | |
181 | |
182 def CleanLogs(context): | |
183 """ | |
184 Cleans the logs on the device. | |
185 """ | |
186 context.device.RunShellCommand('logcat -c') | |
187 | |
188 | |
189 def ShowLogs(): | |
190 """ | |
191 Displays the log for the mojo shell. | |
192 | |
193 Returns the process responsible for reading the logs. | |
194 """ | |
195 logcat = subprocess.Popen([constants.GetAdbPath(), | |
196 'logcat', | |
197 '-s', | |
198 ' '.join(LOGCAT_TAGS)], | |
199 stdout=sys.stdout) | |
200 atexit.register(_ExitIfNeeded, logcat) | |
201 return logcat | |
202 | |
203 | |
204 def GetFilePath(filename): | |
205 """ | |
206 Returns a path suitable for the application to create a file. | |
207 """ | |
208 return '/data/data/%s/files/%s' % (MOJO_SHELL_PACKAGE_NAME, filename) | |
OLD | NEW |