| 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 |