Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(66)

Unified Diff: mojo/tools/mopy/android.py

Issue 1100903003: Move android.py to devtools. (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « mojo/tools/devtools.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: mojo/tools/mopy/android.py
diff --git a/mojo/tools/mopy/android.py b/mojo/tools/mopy/android.py
deleted file mode 100644
index 4db75c068a150edb8d88c2fcbfbf392a76501bdd..0000000000000000000000000000000000000000
--- a/mojo/tools/mopy/android.py
+++ /dev/null
@@ -1,392 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import atexit
-import datetime
-import email.utils
-import hashlib
-import itertools
-import json
-import logging
-import math
-import os
-import os.path
-import random
-import subprocess
-import sys
-import threading
-import time
-import urlparse
-
-import SimpleHTTPServer
-import SocketServer
-
-
-# Tags used by the mojo shell application logs.
-LOGCAT_TAGS = [
- 'AndroidHandler',
- 'MojoFileHelper',
- 'MojoMain',
- 'MojoShellActivity',
- 'MojoShellApplication',
- 'chromium',
-]
-
-MOJO_SHELL_PACKAGE_NAME = 'org.chromium.mojo.shell'
-
-MAPPING_PREFIX = '--map-origin='
-
-DEFAULT_BASE_PORT = 31337
-
-ZERO = datetime.timedelta(0)
-
-class UTC_TZINFO(datetime.tzinfo):
- """UTC time zone representation."""
-
- def utcoffset(self, _):
- return ZERO
-
- def tzname(self, _):
- return "UTC"
-
- def dst(self, _):
- return ZERO
-
-UTC = UTC_TZINFO()
-
-_logger = logging.getLogger()
-
-
-class _SilentTCPServer(SocketServer.TCPServer):
- """
- A TCPServer that won't display any error, unless debugging is enabled. This is
- useful because the client might stop while it is fetching an URL, which causes
- spurious error messages.
- """
- def handle_error(self, request, client_address):
- """
- Override the base class method to have conditional logging.
- """
- if logging.getLogger().isEnabledFor(logging.DEBUG):
- SocketServer.TCPServer.handle_error(self, request, client_address)
-
-
-def _GetHandlerClassForPath(base_path):
- class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
- """
- Handler for SocketServer.TCPServer that will serve the files from
- |base_path| directory over http.
- """
-
- def __init__(self, *args, **kwargs):
- self.etag = None
- SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
-
- def get_etag(self):
- if self.etag:
- return self.etag
-
- path = self.translate_path(self.path)
- if not os.path.isfile(path):
- return None
-
- sha256 = hashlib.sha256()
- BLOCKSIZE = 65536
- with open(path, 'rb') as hashed:
- buf = hashed.read(BLOCKSIZE)
- while len(buf) > 0:
- sha256.update(buf)
- buf = hashed.read(BLOCKSIZE)
- self.etag = '"%s"' % sha256.hexdigest()
- return self.etag
-
- def send_head(self):
- # Always close the connection after each request, as the keep alive
- # support from SimpleHTTPServer doesn't like when the client requests to
- # close the connection before downloading the full response content.
- # pylint: disable=W0201
- self.close_connection = 1
-
- path = self.translate_path(self.path)
- if os.path.isfile(path):
- # Handle If-None-Match
- etag = self.get_etag()
- if ('If-None-Match' in self.headers and
- etag == self.headers['If-None-Match']):
- self.send_response(304)
- return None
-
- # Handle If-Modified-Since
- if ('If-None-Match' not in self.headers and
- 'If-Modified-Since' in self.headers):
- last_modified = datetime.datetime.fromtimestamp(
- math.floor(os.stat(path).st_mtime), tz=UTC)
- ims = datetime.datetime(
- *email.utils.parsedate(self.headers['If-Modified-Since'])[:6],
- tzinfo=UTC)
- if last_modified <= ims:
- self.send_response(304)
- return None
-
- return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
-
- def end_headers(self):
- path = self.translate_path(self.path)
-
- if os.path.isfile(path):
- etag = self.get_etag()
- if etag:
- self.send_header('ETag', etag)
- self.send_header('Cache-Control', 'must-revalidate')
-
- return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self)
-
- def translate_path(self, path):
- path_from_current = (
- SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path))
- return os.path.join(base_path, os.path.relpath(path_from_current))
-
- def log_message(self, *_):
- """
- Override the base class method to disable logging.
- """
- pass
-
- RequestHandler.protocol_version = 'HTTP/1.1'
- return RequestHandler
-
-
-def _IsMapOrigin(arg):
- """Returns whether arg is a --map-origin argument."""
- return arg.startswith(MAPPING_PREFIX)
-
-
-def _Split(l, pred):
- positive = []
- negative = []
- for v in l:
- if pred(v):
- positive.append(v)
- else:
- negative.append(v)
- return (positive, negative)
-
-
-def _ExitIfNeeded(process):
- """
- Exits |process| if it is still alive.
- """
- if process.poll() is None:
- process.kill()
-
-
-class AndroidShell(object):
- """ Allows to set up and run a given mojo shell binary on an Android device.
-
- Args:
- shell_apk_path: path to the shell Android binary
- local_dir: directory where locally build Mojo apps will be served, optional
- adb_path: path to adb, optional if adb is in PATH
- """
- def __init__(self, shell_apk_path, local_dir=None, adb_path="adb"):
- self.shell_apk_path = shell_apk_path
- self.adb_path = adb_path
- self.local_dir = local_dir
-
- def _ReadFifo(self, fifo_path, pipe, on_fifo_closed, max_attempts=5):
- """
- Reads |fifo_path| on the device and write the contents to |pipe|. Calls
- |on_fifo_closed| when the fifo is closed. This method will try to find the
- path up to |max_attempts|, waiting 1 second between each attempt. If it
- cannot find |fifo_path|, a exception will be raised.
- """
- fifo_command = [self.adb_path, 'shell', 'test -e "%s"; echo $?' % fifo_path]
-
- def Run():
- def _WaitForFifo():
- for _ in xrange(max_attempts):
- if subprocess.check_output(fifo_command)[0] == '0':
- return
- time.sleep(1)
- if on_fifo_closed:
- on_fifo_closed()
- raise Exception("Unable to find fifo.")
- _WaitForFifo()
- stdout_cat = subprocess.Popen([self.adb_path,
- 'shell',
- 'cat',
- fifo_path],
- stdout=pipe)
- atexit.register(_ExitIfNeeded, stdout_cat)
- stdout_cat.wait()
- if on_fifo_closed:
- on_fifo_closed()
-
- thread = threading.Thread(target=Run, name="StdoutRedirector")
- thread.start()
-
- def _MapPort(self, device_port, host_port):
- """
- Maps the device port to the host port. If |device_port| is 0, a random
- available port is chosen. Returns the device port.
- """
- def _FindAvailablePortOnDevice():
- opened = subprocess.check_output([self.adb_path, 'shell', 'netstat'])
- opened = [int(x.strip().split()[3].split(':')[1])
- for x in opened if x.startswith(' tcp')]
- while True:
- port = random.randint(4096, 16384)
- if port not in opened:
- return port
- if device_port == 0:
- device_port = _FindAvailablePortOnDevice()
- subprocess.Popen([self.adb_path,
- "reverse",
- "tcp:%d" % device_port,
- "tcp:%d" % host_port]).wait()
-
- unmap_command = [self.adb_path, "reverse", "--remove",
- "tcp:%d" % device_port]
-
- def _UnmapPort():
- subprocess.Popen(unmap_command)
- atexit.register(_UnmapPort)
- return device_port
-
- def _StartHttpServerForDirectory(self, path, port=0):
- """Starts an http server serving files from |path|. Returns the local
- url."""
- assert path
- print 'starting http for', path
- httpd = _SilentTCPServer(('127.0.0.1', 0), _GetHandlerClassForPath(path))
- atexit.register(httpd.shutdown)
-
- http_thread = threading.Thread(target=httpd.serve_forever)
- http_thread.daemon = True
- http_thread.start()
-
- print 'local port=%d' % httpd.server_address[1]
- return 'http://127.0.0.1:%d/' % self._MapPort(port, httpd.server_address[1])
-
- def _StartHttpServerForOriginMapping(self, mapping, port):
- """If |mapping| points at a local file starts an http server to serve files
- from the directory and returns the new mapping.
-
- This is intended to be called for every --map-origin value."""
- parts = mapping.split('=')
- if len(parts) != 2:
- return mapping
- dest = parts[1]
- # If the destination is a url, don't map it.
- if urlparse.urlparse(dest)[0]:
- return mapping
- # Assume the destination is a local file. Start a local server that
- # redirects to it.
- localUrl = self._StartHttpServerForDirectory(dest, port)
- print 'started server at %s for %s' % (dest, localUrl)
- return parts[0] + '=' + localUrl
-
- def _StartHttpServerForOriginMappings(self, map_parameters, fixed_port):
- """Calls _StartHttpServerForOriginMapping for every --map-origin
- argument."""
- if not map_parameters:
- return []
-
- original_values = list(itertools.chain(
- *map(lambda x: x[len(MAPPING_PREFIX):].split(','), map_parameters)))
- sorted(original_values)
- result = []
- for i, value in enumerate(original_values):
- result.append(self._StartHttpServerForOriginMapping(
- value, DEFAULT_BASE_PORT + 1 + i if fixed_port else 0))
- return [MAPPING_PREFIX + ','.join(result)]
-
- def PrepareShellRun(self, origin=None, fixed_port=True):
- """ Prepares for StartShell: runs adb as root and installs the apk. If no
- --origin is specified, local http server will be set up to serve files from
- the build directory along with port forwarding.
-
- Returns arguments that should be appended to shell argument list."""
- if 'cannot run as root' in subprocess.check_output([self.adb_path, 'root']):
- raise Exception("Unable to run adb as root.")
- subprocess.check_call(
- [self.adb_path, 'install', '-r', self.shell_apk_path, '-i',
- MOJO_SHELL_PACKAGE_NAME])
- atexit.register(self.StopShell)
-
- extra_shell_args = []
- origin_url = origin if origin else self._StartHttpServerForDirectory(
- self.local_dir, DEFAULT_BASE_PORT if fixed_port else 0)
- extra_shell_args.append("--origin=" + origin_url)
-
- return extra_shell_args
-
- def StartShell(self,
- arguments,
- stdout=None,
- on_application_stop=None,
- fixed_port=True):
- """
- Starts the mojo shell, passing it the given arguments.
-
- The |arguments| list must contain the "--origin=" arg from PrepareShellRun.
- If |stdout| is not None, it should be a valid argument for subprocess.Popen.
- """
- STDOUT_PIPE = "/data/data/%s/stdout.fifo" % MOJO_SHELL_PACKAGE_NAME
-
- cmd = [self.adb_path,
- 'shell',
- 'am',
- 'start',
- '-S',
- '-a', 'android.intent.action.VIEW',
- '-n', '%s/.MojoShellActivity' % MOJO_SHELL_PACKAGE_NAME]
-
- parameters = []
- if stdout or on_application_stop:
- subprocess.check_call([self.adb_path, 'shell', 'rm', STDOUT_PIPE])
- parameters.append('--fifo-path=%s' % STDOUT_PIPE)
- self._ReadFifo(STDOUT_PIPE, stdout, on_application_stop)
- # The origin has to be specified whether it's local or external.
- assert any("--origin=" in arg for arg in arguments)
-
- # Extract map-origin arguments.
- map_parameters, other_parameters = _Split(arguments, _IsMapOrigin)
- parameters += other_parameters
- parameters += self._StartHttpServerForOriginMappings(map_parameters,
- fixed_port)
-
- if parameters:
- encodedParameters = json.dumps(parameters)
- cmd += ['--es', 'encodedParameters', encodedParameters]
-
- with open(os.devnull, 'w') as devnull:
- subprocess.Popen(cmd, stdout=devnull).wait()
-
- def StopShell(self):
- """
- Stops the mojo shell.
- """
- subprocess.check_call(
- [self.adb_path, 'shell', 'am', 'force-stop', MOJO_SHELL_PACKAGE_NAME])
-
- def CleanLogs(self):
- """
- Cleans the logs on the device.
- """
- subprocess.check_call([self.adb_path, 'logcat', '-c'])
-
- def ShowLogs(self):
- """
- Displays the log for the mojo shell.
-
- Returns the process responsible for reading the logs.
- """
- logcat = subprocess.Popen([self.adb_path,
- 'logcat',
- '-s',
- ' '.join(LOGCAT_TAGS)],
- stdout=sys.stdout)
- atexit.register(_ExitIfNeeded, logcat)
- return logcat
« no previous file with comments | « mojo/tools/devtools.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698