auto import from //depot/cupcake/@136654
diff --git a/testrunner/adb_interface.py b/testrunner/adb_interface.py
new file mode 100755
index 0000000..fb304df
--- /dev/null
+++ b/testrunner/adb_interface.py
@@ -0,0 +1,347 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2008, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Provides an interface to communicate with the device via the adb command.
+
+Assumes adb binary is currently on system path.
+"""
+# Python imports
+import os
+import string
+import time
+
+# local imports
+import am_instrument_parser
+import errors
+import logger
+import run_command
+
+
+class AdbInterface:
+  """Helper class for communicating with Android device via adb."""
+
+  # argument to pass to adb, to direct command to specific device
+  _target_arg = ""
+
+  DEVICE_TRACE_DIR = "/data/test_results/"
+
+  def SetEmulatorTarget(self):
+    """Direct all future commands to the only running emulator."""
+    self._target_arg = "-e"
+
+  def SetDeviceTarget(self):
+    """Direct all future commands to the only connected USB device."""
+    self._target_arg = "-d"
+
+  def SetTargetSerial(self, serial):
+    """Direct all future commands to Android target with the given serial."""
+    self._target_arg = "-s %s" % serial
+
+  def SendCommand(self, command_string, timeout_time=20, retry_count=3):
+    """Send a command via adb.
+
+    Args:
+      command_string: adb command to run
+      timeout_time: number of seconds to wait for command to respond before
+        retrying
+      retry_count: number of times to retry command before raising
+        WaitForResponseTimedOutError
+    Returns:
+      string output of command
+
+    Raises:
+      WaitForResponseTimedOutError if device does not respond to command
+    """
+    adb_cmd = "adb %s %s" % (self._target_arg, command_string)
+    logger.SilentLog("about to run %s" % adb_cmd)
+    return run_command.RunCommand(adb_cmd, timeout_time=timeout_time,
+                                  retry_count=retry_count)
+
+  def SendShellCommand(self, cmd, timeout_time=20, retry_count=3):
+    """Send a adb shell command.
+
+    Args:
+      cmd: adb shell command to run
+      timeout_time: number of seconds to wait for command to respond before
+        retrying
+      retry_count: number of times to retry command before raising
+        WaitForResponseTimedOutError
+
+    Returns:
+      string output of command
+
+    Raises:
+      WaitForResponseTimedOutError: if device does not respond to command
+    """
+    return self.SendCommand("shell %s" % cmd, timeout_time=timeout_time,
+                            retry_count=retry_count)
+
+  def BugReport(self, path):
+    """Dumps adb bugreport to the file specified by the path.
+
+    Args:
+      path: Path of the file where adb bugreport is dumped to.
+    """
+    bug_output = self.SendShellCommand("bugreport", timeout_time=60)
+    bugreport_file = open(path, "w")
+    bugreport_file.write(bug_output)
+    bugreport_file.close()
+
+  def Push(self, src, dest):
+    """Pushes the file src onto the device at dest.
+
+    Args:
+      src: file path of host file to push
+      dest: destination absolute file path on device
+    """
+    self.SendCommand("push %s %s" % (src, dest), timeout_time=60)
+
+  def Pull(self, src, dest):
+    """Pulls the file src on the device onto dest on the host.
+
+    Args:
+      src: absolute file path of file on device to pull
+      dest: destination file path on host
+
+    Returns:
+      True if success and False otherwise.
+    """
+    # Create the base dir if it doesn't exist already
+    if not os.path.exists(os.path.dirname(dest)):
+      os.makedirs(os.path.dirname(dest))
+
+    if self.DoesFileExist(src):
+      self.SendCommand("pull %s %s" % (src, dest), timeout_time=60)
+      return True
+    else:
+      logger.Log("ADB Pull Failed: Source file %s does not exist." % src)
+      return False
+
+  def DoesFileExist(self, src):
+    """Checks if the given path exists on device target.
+
+    Args:
+      src: file path to be checked.
+
+    Returns:
+      True if file exists
+    """
+
+    output = self.SendShellCommand("ls %s" % src)
+    error = "No such file or directory"
+
+    if error in output:
+      return False
+    return True
+
+  def StartInstrumentationForPackage(
+      self, package_name, runner_name, timeout_time=60*10,
+      no_window_animation=False, instrumentation_args={}):
+    """Run instrumentation test for given package and runner.
+
+    Equivalent to StartInstrumentation, except instrumentation path is
+    separated into its package and runner components.
+    """
+    instrumentation_path = "%s/%s" % (package_name, runner_name)
+    return self.StartInstrumentation(self, instrumentation_path, timeout_time,
+                                     no_window_animation, instrumentation_args)
+
+  def StartInstrumentation(
+      self, instrumentation_path, timeout_time=60*10, no_window_animation=False,
+      profile=False, instrumentation_args={}):
+
+    """Runs an instrumentation class on the target.
+
+    Returns a dictionary containing the key value pairs from the
+    instrumentations result bundle and a list of TestResults. Also handles the
+    interpreting of error output from the device and raises the necessary
+    exceptions.
+
+    Args:
+      instrumentation_path: string. It should be the fully classified package
+      name, and instrumentation test runner, separated by "/"
+        e.g. com.android.globaltimelaunch/.GlobalTimeLaunch
+      timeout_time: Timeout value for the am command.
+      no_window_animation: boolean, Whether you want window animations enabled
+        or disabled
+      profile: If True, profiling will be turned on for the instrumentation.
+      instrumentation_args: Dictionary of key value bundle arguments to pass to
+      instrumentation.
+
+    Returns:
+      (test_results, inst_finished_bundle)
+
+      test_results: a list of TestResults
+      inst_finished_bundle (dict): Key/value pairs contained in the bundle that
+        is passed into ActivityManager.finishInstrumentation(). Included in this
+        bundle is the return code of the Instrumentation process, any error
+        codes reported by the activity manager, and any results explicitly added
+        by the instrumentation code.
+
+     Raises:
+       WaitForResponseTimedOutError: if timeout occurred while waiting for
+         response to adb instrument command
+       DeviceUnresponsiveError: if device system process is not responding
+       InstrumentationError: if instrumentation failed to run
+    """
+
+    command_string = self._BuildInstrumentationCommandPath(
+        instrumentation_path, no_window_animation=no_window_animation,
+        profile=profile, raw_mode=True,
+        instrumentation_args=instrumentation_args)
+
+    (test_results, inst_finished_bundle) = (
+        am_instrument_parser.ParseAmInstrumentOutput(
+            self.SendShellCommand(command_string, timeout_time=timeout_time,
+                                  retry_count=2)))
+
+    if "code" not in inst_finished_bundle:
+      raise errors.InstrumentationError("no test results... device setup "
+                                        "correctly?")
+
+    if inst_finished_bundle["code"] == "0":
+      short_msg_result = "no error message"
+      if "shortMsg" in inst_finished_bundle:
+        short_msg_result = inst_finished_bundle["shortMsg"]
+        logger.Log(short_msg_result)
+      raise errors.InstrumentationError(short_msg_result)
+
+    if "INSTRUMENTATION_ABORTED" in inst_finished_bundle:
+      logger.Log("INSTRUMENTATION ABORTED!")
+      raise errors.DeviceUnresponsiveError
+
+    return (test_results, inst_finished_bundle)
+
+  def StartInstrumentationNoResults(
+      self, package_name, runner_name, no_window_animation=False,
+      raw_mode=False, instrumentation_args={}):
+    """Runs instrumentation and dumps output to stdout.
+
+    Equivalent to StartInstrumentation, but will dump instrumentation
+    'normal' output to stdout, instead of parsing return results. Command will
+    never timeout.
+    """
+    adb_command_string = self.PreviewInstrumentationCommand(
+        package_name, runner_name, no_window_animation=no_window_animation,
+        raw_mode=raw_mode, instrumentation_args=instrumentation_args)
+    logger.Log(adb_command_string)
+    run_command.RunCommand(adb_command_string, return_output=False)
+
+  def PreviewInstrumentationCommand(
+      self, package_name, runner_name, no_window_animation=False,
+      raw_mode=False, instrumentation_args={}):
+    """Returns a string of adb command that will be executed."""
+    inst_command_string = self._BuildInstrumentationCommand(
+        package_name, runner_name, no_window_animation=no_window_animation,
+        raw_mode=raw_mode, instrumentation_args=instrumentation_args)
+    command_string = "adb %s shell %s" % (self._target_arg, inst_command_string)
+    return command_string
+
+  def _BuildInstrumentationCommand(
+      self, package, runner_name, no_window_animation=False, profile=False,
+      raw_mode=True, instrumentation_args={}):
+    instrumentation_path = "%s/%s" % (package, runner_name)
+
+    return self._BuildInstrumentationCommandPath(
+        instrumentation_path, no_window_animation=no_window_animation,
+        profile=profile, raw_mode=raw_mode,
+        instrumentation_args=instrumentation_args)
+
+  def _BuildInstrumentationCommandPath(
+      self, instrumentation_path, no_window_animation=False, profile=False,
+      raw_mode=True, instrumentation_args={}):
+    command_string = "am instrument"
+    if no_window_animation:
+      command_string += " --no_window_animation"
+    if profile:
+      self._CreateTraceDir()
+      command_string += (
+          " -p %s/%s.dmtrace" %
+          (self.DEVICE_TRACE_DIR, instrumentation_path.split(".")[-1]))
+
+    for key, value in instrumentation_args.items():
+      command_string += " -e %s %s" % (key, value)
+    if raw_mode:
+      command_string += " -r"
+    command_string += " -w %s" % instrumentation_path
+    return command_string
+
+  def _CreateTraceDir(self):
+    ls_response = self.SendShellCommand("ls /data/trace")
+    if ls_response.strip("#").strip(string.whitespace) != "":
+      self.SendShellCommand("create /data/trace", "mkdir /data/trace")
+      self.SendShellCommand("make /data/trace world writeable",
+                            "chmod 777 /data/trace")
+
+  def WaitForDevicePm(self, wait_time=120):
+    """Waits for targeted device's package manager to be up.
+
+    Args:
+      wait_time: time in seconds to wait
+
+    Raises:
+      WaitForResponseTimedOutError if wait_time elapses and pm still does not
+      respond.
+    """
+    logger.Log("Waiting for device package manager for %s seconds..."
+               % wait_time)
+    self.SendCommand("wait-for-device")
+    # Now the device is there, but may not be running.
+    # Query the package manager with a basic command
+    pm_found = False
+    attempts = 0
+    wait_period = 5
+    while not pm_found and (attempts*wait_period) < wait_time:
+      # assume the 'adb shell pm path android' command will always 
+      # return 'package: something' in the success case
+      output = self.SendShellCommand("pm path android", retry_count=1)
+      if "package:" in output:
+        pm_found = True
+      else:
+        time.sleep(wait_period)
+        attempts += 1
+    if not pm_found:
+      raise errors.WaitForResponseTimedOutError
+    
+  def Sync(self, retry_count=3):
+    """Perform a adb sync.
+    
+    Blocks until device package manager is responding.
+    
+    Args:
+      retry_count: number of times to retry sync before failing
+      
+    Raises:
+      WaitForResponseTimedOutError if package manager does not respond
+    """
+    output = self.SendCommand("sync", retry_count=retry_count)
+    if "Read-only file system" in output:
+      logger.SilentLog(output) 
+      logger.Log("adb sync failed due to read only fs, retrying")
+      self.SendCommand("remount")
+      output = self.SendCommand("sync", retry_count=retry_count)
+    if "No space left on device" in output:
+      logger.SilentLog(output) 
+      logger.Log("adb sync failed due to no space on device, trying shell" + 
+                 " start/stop")
+      self.SendShellCommand("stop", retry_count=retry_count)
+      output = self.SendCommand("sync", retry_count=retry_count)
+      self.SendShellCommand("start", retry_count=retry_count)
+
+    logger.SilentLog(output)
+    self.WaitForDevicePm()
+    return output
diff --git a/testrunner/am_instrument_parser.py b/testrunner/am_instrument_parser.py
new file mode 100755
index 0000000..cad87c0
--- /dev/null
+++ b/testrunner/am_instrument_parser.py
@@ -0,0 +1,178 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2008, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Module that assists in parsing the output of "am instrument" commands run on
+the device."""
+
+import re
+import string
+
+
+def ParseAmInstrumentOutput(result):
+  """Given the raw output of an "am instrument" command that targets and
+  InstrumentationTestRunner, return structured data.
+
+  Args:
+    result (string): Raw output of "am instrument"
+
+  Return
+  (test_results, inst_finished_bundle)
+  
+  test_results (list of am_output_parser.TestResult)
+  inst_finished_bundle (dict): Key/value pairs contained in the bundle that is
+    passed into ActivityManager.finishInstrumentation(). Included in this bundle is the return
+    code of the Instrumentation process, any error codes reported by the
+    activity manager, and any results explicity added by the instrumentation
+    code.
+  """
+
+  re_status_code = re.compile(r'INSTRUMENTATION_STATUS_CODE: (?P<status_code>-?\d)$')
+  test_results = []
+  inst_finished_bundle = {}
+
+  result_block_string = ""
+  for line in result.splitlines():
+    result_block_string += line + '\n'
+
+    if "INSTRUMENTATION_STATUS_CODE:" in line:
+      test_result = TestResult(result_block_string)
+      if test_result.GetStatusCode() == 1: # The test started
+        pass
+      elif test_result.GetStatusCode() in [0, -1, -2]:
+        test_results.append(test_result)
+      else:
+        pass
+      result_block_string = ""
+    if "INSTRUMENTATION_CODE:" in line:
+      inst_finished_bundle = _ParseInstrumentationFinishedBundle(result_block_string)
+      result_block_string = ""
+
+  return (test_results, inst_finished_bundle)
+
+
+def _ParseInstrumentationFinishedBundle(result):
+  """Given the raw output of "am instrument" returns a dictionary of the
+  key/value pairs from the bundle passed into 
+  ActivityManager.finishInstrumentation().
+
+  Args:
+    result (string): Raw output of "am instrument"
+
+  Return:
+  inst_finished_bundle (dict): Key/value pairs contained in the bundle that is
+    passed into ActivityManager.finishInstrumentation(). Included in this bundle is the return
+    code of the Instrumentation process, any error codes reported by the
+    activity manager, and any results explicity added by the instrumentation
+    code.
+  """
+
+  re_result = re.compile(r'INSTRUMENTATION_RESULT: ([^=]+)=(.+)$')
+  re_code = re.compile(r'INSTRUMENTATION_CODE: (\-?\d)$')
+  result_dict = {}
+  key = ''
+  val = ''
+  last_tag = ''
+
+  for line in result.split('\n'):
+    line = line.strip(string.whitespace)
+    if re_result.match(line):
+      last_tag = 'INSTRUMENTATION_RESULT'
+      key = re_result.search(line).group(1).strip(string.whitespace)
+      if key.startswith('performance.'):
+        key = key[len('performance.'):]
+      val = re_result.search(line).group(2).strip(string.whitespace)
+      try:
+        result_dict[key] = float(val)
+      except ValueError:
+        result_dict[key] = val
+      except TypeError:
+        result_dict[key] = val
+    elif re_code.match(line):
+      last_tag = 'INSTRUMENTATION_CODE'
+      key = 'code'
+      val = re_code.search(line).group(1).strip(string.whitespace)
+      result_dict[key] = val
+    elif 'INSTRUMENTATION_ABORTED:' in line:
+      last_tag = 'INSTRUMENTATION_ABORTED'
+      key = 'INSTRUMENTATION_ABORTED'
+      val = ''
+      result_dict[key] = val
+    elif last_tag == 'INSTRUMENTATION_RESULT':
+      result_dict[key] += '\n' + line
+
+  if not result_dict.has_key('code'):
+    result_dict['code'] = '0'
+    result_dict['shortMsg'] = "No result returned from instrumentation"
+
+  return result_dict
+
+
+class TestResult(object):
+  """A class that contains information about a single test result."""
+
+  def __init__(self, result_block_string):
+    """
+    Args:
+      result_block_string (string): Is a single "block" of output. A single
+      "block" would be either a "test started" status report, or a "test
+      finished" status report.
+    """
+
+    self._test_name = None
+    self._status_code = None
+    self._failure_reason = None
+
+    re_start_block = re.compile(
+       r'\s*INSTRUMENTATION_STATUS: stream=(?P<stream>.*)'
+        'INSTRUMENTATION_STATUS: test=(?P<test>\w+)\s+'
+        'INSTRUMENTATION_STATUS: class=(?P<class>[\w\.]+)\s+'
+        'INSTRUMENTATION_STATUS: current=(?P<current>\d+)\s+'
+        'INSTRUMENTATION_STATUS: numtests=(?P<numtests>\d+)\s+'
+        'INSTRUMENTATION_STATUS: id=.*\s+'
+        'INSTRUMENTATION_STATUS_CODE: 1\s*', re.DOTALL)
+
+    re_end_block = re.compile(
+       r'\s*INSTRUMENTATION_STATUS: stream=(?P<stream>.*)'
+        'INSTRUMENTATION_STATUS: test=(?P<test>\w+)\s+'
+        '(INSTRUMENTATION_STATUS: stack=(?P<stack>.*))?'
+        'INSTRUMENTATION_STATUS: class=(?P<class>[\w\.]+)\s+'
+        'INSTRUMENTATION_STATUS: current=(?P<current>\d+)\s+'
+        'INSTRUMENTATION_STATUS: numtests=(?P<numtests>\d+)\s+'
+        'INSTRUMENTATION_STATUS: id=.*\s+'
+        'INSTRUMENTATION_STATUS_CODE: (?P<status_code>0|-1|-2)\s*', re.DOTALL)
+
+    start_block_match = re_start_block.match(result_block_string)
+    end_block_match = re_end_block.match(result_block_string)
+
+    if start_block_match:
+      self._test_name = "%s:%s" % (start_block_match.group('class'),
+                                   start_block_match.group('test'))
+      self._status_code = 1
+    elif end_block_match:
+      self._test_name = "%s:%s" % (end_block_match.group('class'),
+                                   end_block_match.group('test'))
+      self._status_code = int(end_block_match.group('status_code'))
+      self._failure_reason = end_block_match.group('stack')
+
+  def GetTestName(self):
+    return self._test_name
+
+  def GetStatusCode(self):
+    return self._status_code
+
+  def GetFailureReason(self):
+    return self._failure_reason
diff --git a/testrunner/coverage.py b/testrunner/coverage.py
new file mode 100755
index 0000000..507c5c7
--- /dev/null
+++ b/testrunner/coverage.py
@@ -0,0 +1,312 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2008, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); 
+# you may not use this file except in compliance with the License. 
+# You may obtain a copy of the License at 
+#
+#     http://www.apache.org/licenses/LICENSE-2.0 
+#
+# Unless required by applicable law or agreed to in writing, software 
+# distributed under the License is distributed on an "AS IS" BASIS, 
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+# See the License for the specific language governing permissions and 
+# limitations under the License.
+
+"""Utilities for generating code coverage reports for Android tests."""
+
+# Python imports
+import glob
+import optparse
+import os
+
+# local imports
+import android_build
+import coverage_targets
+import errors
+import logger
+import run_command
+
+
+class CoverageGenerator(object):
+  """Helper utility for obtaining code coverage results on Android.
+
+  Intended to simplify the process of building,running, and generating code
+  coverage results for a pre-defined set of tests and targets
+  """
+
+  # environment variable to enable emma builds in Android build system
+  _EMMA_BUILD_FLAG = "EMMA_INSTRUMENT"
+  # build path to Emma target Makefile
+  _EMMA_BUILD_PATH = os.path.join("external", "emma")
+    # path to EMMA host jar, relative to Android build root
+  _EMMA_JAR = os.path.join(_EMMA_BUILD_PATH, "lib", "emma.jar")
+  _TEST_COVERAGE_EXT = "ec"
+  # default device-side path to code coverage results file
+  _DEVICE_COVERAGE_PATH = "/sdcard/coverage.ec"
+  # root path of generated coverage report files, relative to Android build root
+  _COVERAGE_REPORT_PATH = os.path.join("out", "emma")
+  _CORE_TARGET_PATH = os.path.join("development", "testrunner",
+                                   "coverage_targets.xml")
+  # vendor glob file path patterns to tests, relative to android
+  # build root
+  _VENDOR_TARGET_PATH = os.path.join("vendor", "*", "tests", "testinfo",
+                                     "coverage_targets.xml")
+
+  # path to root of target build intermediates
+  _TARGET_INTERMEDIATES_BASE_PATH = os.path.join("out", "target", "common",
+                                                 "obj")
+
+  def __init__(self, android_root_path, adb_interface):
+    self._root_path = android_root_path
+    self._output_root_path = os.path.join(self._root_path,
+                                          self._COVERAGE_REPORT_PATH)
+    self._emma_jar_path = os.path.join(self._root_path, self._EMMA_JAR)
+    self._adb = adb_interface
+    self._targets_manifest = self._ReadTargets()
+
+  def EnableCoverageBuild(self):
+    """Enable building an Android target with code coverage instrumentation."""
+    os.environ[self._EMMA_BUILD_FLAG] = "true"
+
+  def ExtractReport(self, test_suite,
+                    device_coverage_path=_DEVICE_COVERAGE_PATH,
+                    output_path=None):
+    """Extract runtime coverage data and generate code coverage report.
+
+    Assumes test has just been executed.
+    Args:
+      test_suite: TestSuite to generate coverage data for
+      device_coverage_path: location of coverage file on device
+      output_path: path to place output files in. If None will use
+        <android_root_path>/<_COVERAGE_REPORT_PATH>/<target>/<test>
+
+    Returns:
+      absolute file path string of generated html report file.
+    """
+    if output_path is None:
+      output_path = os.path.join(self._root_path,
+                                 self._COVERAGE_REPORT_PATH,
+                                 test_suite.GetTargetName(),
+                                 test_suite.GetName())
+
+    coverage_local_name = "%s.%s" % (test_suite.GetName(),
+                                     self._TEST_COVERAGE_EXT)
+    coverage_local_path = os.path.join(output_path,
+                                       coverage_local_name)
+    if self._adb.Pull(device_coverage_path, coverage_local_path):
+
+      report_path = os.path.join(output_path,
+                                 test_suite.GetName())
+      target = self._targets_manifest.GetTarget(test_suite.GetTargetName())
+      return self._GenerateReport(report_path, coverage_local_path, [target],
+                                  do_src=True)
+    return None
+
+  def _GenerateReport(self, report_path, coverage_file_path, targets,
+                      do_src=True):
+    """Generate the code coverage report.
+
+    Args:
+      report_path: absolute file path of output file, without extension
+      coverage_file_path: absolute file path of code coverage result file
+      targets: list of CoverageTargets to use as base for code coverage
+          measurement.
+      do_src: True if generate coverage report with source linked in.
+          Note this will increase size of generated report.
+
+    Returns:
+      absolute file path to generated report file.
+    """
+    input_metadatas = self._GatherMetadatas(targets)
+
+    if do_src:
+      src_arg = self._GatherSrcs(targets)
+    else:
+      src_arg = ""
+
+    report_file = "%s.html" % report_path
+    cmd1 = ("java -cp %s emma report -r html -in %s %s %s " %
+            (self._emma_jar_path, coverage_file_path, input_metadatas, src_arg))
+    cmd2 = "-Dreport.html.out.file=%s" % report_file
+    self._RunCmd(cmd1 + cmd2)
+    return report_file
+
+  def _GatherMetadatas(self, targets):
+    """Builds the emma input metadata argument from provided targets.
+
+    Args:
+      targets: list of CoverageTargets
+
+    Returns:
+      input metadata argument string
+    """
+    input_metadatas = ""
+    for target in targets:
+      input_metadata = os.path.join(self._GetBuildIntermediatePath(target),
+                                    "coverage.em")
+      input_metadatas += " -in %s" % input_metadata
+    return input_metadatas
+
+  def _GetBuildIntermediatePath(self, target):
+    return os.path.join(
+        self._root_path, self._TARGET_INTERMEDIATES_BASE_PATH, target.GetType(),
+        "%s_intermediates" % target.GetName())
+
+  def _GatherSrcs(self, targets):
+    """Builds the emma input source path arguments from provided targets.
+
+    Args:
+      targets: list of CoverageTargets
+    Returns:
+      source path arguments string
+    """
+    src_list = []
+    for target in targets:
+      target_srcs = target.GetPaths()
+      for path in target_srcs:
+        src_list.append("-sp %s" %  os.path.join(self._root_path, path))
+    return " ".join(src_list)
+
+  def _MergeFiles(self, input_paths, dest_path):
+    """Merges a set of emma coverage files into a consolidated file.
+
+    Args:
+      input_paths: list of string absolute coverage file paths to merge
+      dest_path: absolute file path of destination file
+    """
+    input_list = []
+    for input_path in input_paths:
+      input_list.append("-in %s" % input_path)
+    input_args = " ".join(input_list)
+    self._RunCmd("java -cp %s emma merge %s -out %s" % (self._emma_jar_path,
+                                                        input_args, dest_path))
+
+  def _RunCmd(self, cmd):
+    """Runs and logs the given os command."""
+    run_command.RunCommand(cmd, return_output=False)
+
+  def _CombineTargetCoverage(self):
+    """Combines all target mode code coverage results.
+
+    Will find all code coverage data files in direct sub-directories of
+    self._output_root_path, and combine them into a single coverage report.
+    Generated report is placed at self._output_root_path/android.html
+    """
+    coverage_files = self._FindCoverageFiles(self._output_root_path)
+    combined_coverage = os.path.join(self._output_root_path,
+                                     "android.%s" % self._TEST_COVERAGE_EXT)
+    self._MergeFiles(coverage_files, combined_coverage)
+    report_path = os.path.join(self._output_root_path, "android")
+    # don't link to source, to limit file size
+    self._GenerateReport(report_path, combined_coverage,
+                         self._targets_manifest.GetTargets(), do_src=False)
+
+  def _CombineTestCoverage(self):
+    """Consolidates code coverage results for all target result directories."""
+    target_dirs = os.listdir(self._output_root_path)
+    for target_name in target_dirs:
+      output_path = os.path.join(self._output_root_path, target_name)
+      target = self._targets_manifest.GetTarget(target_name)
+      if os.path.isdir(output_path) and target is not None:
+        coverage_files = self._FindCoverageFiles(output_path)
+        combined_coverage = os.path.join(output_path, "%s.%s" %
+                                         (target_name, self._TEST_COVERAGE_EXT))
+        self._MergeFiles(coverage_files, combined_coverage)
+        report_path = os.path.join(output_path, target_name)
+        self._GenerateReport(report_path, combined_coverage, [target])
+      else:
+        logger.Log("%s is not a valid target directory, skipping" % output_path)
+
+  def _FindCoverageFiles(self, root_path):
+    """Finds all files in <root_path>/*/*.<_TEST_COVERAGE_EXT>.
+
+    Args:
+      root_path: absolute file path string to search from
+    Returns:
+      list of absolute file path strings of coverage files
+    """
+    file_pattern = os.path.join(root_path, "*", "*.%s" %
+                                self._TEST_COVERAGE_EXT)
+    coverage_files = glob.glob(file_pattern)
+    return coverage_files
+
+  def GetEmmaBuildPath(self):
+    return self._EMMA_BUILD_PATH
+
+  def _ReadTargets(self):
+    """Parses the set of coverage target data.
+
+    Returns:
+       a CoverageTargets object that contains set of parsed targets.
+    Raises:
+       AbortError if a fatal error occurred when parsing the target files.
+    """
+    core_target_path = os.path.join(self._root_path, self._CORE_TARGET_PATH)
+    try:
+      targets = coverage_targets.CoverageTargets()
+      targets.Parse(core_target_path)
+      vendor_targets_pattern = os.path.join(self._root_path,
+                                            self._VENDOR_TARGET_PATH)
+      target_file_paths = glob.glob(vendor_targets_pattern)
+      for target_file_path in target_file_paths:
+        targets.Parse(target_file_path)
+      return targets
+    except errors.ParseError:
+      raise errors.AbortError
+
+  def TidyOutput(self):
+    """Runs tidy on all generated html files.
+
+    This is needed to the html files can be displayed cleanly on a web server.
+    Assumes tidy is on current PATH.
+    """
+    logger.Log("Tidying output files")
+    self._TidyDir(self._output_root_path)
+
+  def _TidyDir(self, dir_path):
+    """Recursively tidy all html files in given dir_path."""
+    html_file_pattern = os.path.join(dir_path, "*.html")
+    html_files_iter = glob.glob(html_file_pattern)
+    for html_file_path in html_files_iter:
+      os.system("tidy -m -errors -quiet %s" % html_file_path)
+    sub_dirs = os.listdir(dir_path)
+    for sub_dir_name in sub_dirs:
+      sub_dir_path = os.path.join(dir_path, sub_dir_name)
+      if os.path.isdir(sub_dir_path):
+        self._TidyDir(sub_dir_path)
+
+  def CombineCoverage(self):
+    """Create combined coverage reports for all targets and tests."""
+    self._CombineTestCoverage()
+    self._CombineTargetCoverage()
+
+
+def Run():
+  """Does coverage operations based on command line args."""
+  # TODO: do we want to support combining coverage for a single target
+
+  try:
+    parser = optparse.OptionParser(usage="usage: %prog --combine-coverage")
+    parser.add_option(
+        "-c", "--combine-coverage", dest="combine_coverage", default=False,
+        action="store_true", help="Combine coverage results stored given "
+        "android root path")
+    parser.add_option(
+        "-t", "--tidy", dest="tidy", default=False, action="store_true",
+        help="Run tidy on all generated html files")
+
+    options, args = parser.parse_args()
+
+    coverage = CoverageGenerator(android_build.GetTop(), None)
+    if options.combine_coverage:
+      coverage.CombineCoverage()
+    if options.tidy:
+      coverage.TidyOutput()
+  except errors.AbortError:
+    logger.SilentLog("Exiting due to AbortError")
+
+if __name__ == "__main__":
+  Run()
diff --git a/testrunner/errors.py b/testrunner/errors.py
new file mode 100755
index 0000000..6d606ec
--- /dev/null
+++ b/testrunner/errors.py
@@ -0,0 +1,40 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2008, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); 
+# you may not use this file except in compliance with the License. 
+# You may obtain a copy of the License at 
+#
+#     http://www.apache.org/licenses/LICENSE-2.0 
+#
+# Unless required by applicable law or agreed to in writing, software 
+# distributed under the License is distributed on an "AS IS" BASIS, 
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+# See the License for the specific language governing permissions and 
+# limitations under the License.
+
+"""Defines common exception classes for this package."""
+
+
+class WaitForResponseTimedOutError(Exception):
+  """We sent a command and had to wait too long for response."""
+
+
+class DeviceUnresponsiveError(Exception):
+  """Device is unresponsive to command."""
+  
+
+class InstrumentationError(Exception):
+  """Failed to run instrumentation."""
+
+
+class AbortError(Exception):
+  """Generic exception that indicates a fatal error has occurred and program
+  execution should be aborted."""
+
+
+class ParseError(Exception):
+  """Raised when xml data to parse has unrecognized format."""
+
diff --git a/testrunner/logger.py b/testrunner/logger.py
new file mode 100755
index 0000000..762c893
--- /dev/null
+++ b/testrunner/logger.py
@@ -0,0 +1,85 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2007, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Simple logging utility. Dumps log messages to stdout, and optionally, to a
+log file.
+
+Init(path) must be called to enable logging to a file
+"""
+
+import datetime
+
+_LOG_FILE = None
+_verbose = False
+
+def Init(log_file_path):
+  """Set the path to the log file"""
+  global _LOG_FILE
+  _LOG_FILE = log_file_path
+  print "Using log file: %s" % _LOG_FILE
+
+def GetLogFilePath():
+  """Returns the path and name of the Log file"""
+  global _LOG_FILE
+  return _LOG_FILE
+
+def Log(new_str):
+  """Appends new_str to the end of _LOG_FILE and prints it to stdout.
+
+  Args:
+    # new_str is a string.
+    new_str: 'some message to log'
+  """
+  msg = _PrependTimeStamp(new_str)
+  print msg
+  _WriteLog(msg)
+
+def _WriteLog(msg):
+  global _LOG_FILE
+  if _LOG_FILE is not None:
+    file_handle = file(_LOG_FILE, 'a')
+    file_handle.write('\n' + str(msg))
+    file_handle.close()
+
+def _PrependTimeStamp(log_string):
+  """Returns the log_string prepended with current timestamp """
+  return "# %s: %s" % (datetime.datetime.now().strftime("%m/%d/%y %H:%M:%S"),
+      log_string)
+
+def SilentLog(new_str):
+  """Silently log new_str. Unless verbose mode is enabled, will log new_str
+    only to the log file
+  Args:
+    # new_str is a string.
+    new_str: 'some message to log'
+  """
+  global _verbose
+  msg = _PrependTimeStamp(new_str)
+  if _verbose:
+    print msg
+  _WriteLog(msg)
+
+def SetVerbose(new_verbose=True):
+  """ Enable or disable verbose logging"""
+  global _verbose
+  _verbose = new_verbose
+
+def main():
+  pass
+
+if __name__ == '__main__':
+  main()
diff --git a/testrunner/run_command.py b/testrunner/run_command.py
new file mode 100755
index 0000000..6b72b77
--- /dev/null
+++ b/testrunner/run_command.py
@@ -0,0 +1,117 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2007, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); 
+# you may not use this file except in compliance with the License. 
+# You may obtain a copy of the License at 
+#
+#     http://www.apache.org/licenses/LICENSE-2.0 
+#
+# Unless required by applicable law or agreed to in writing, software 
+# distributed under the License is distributed on an "AS IS" BASIS, 
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+# See the License for the specific language governing permissions and 
+# limitations under the License.
+
+# System imports
+import os
+import signal
+import subprocess
+import time
+import threading
+
+# local imports
+import logger
+import errors
+
+_abort_on_error = False
+
+def SetAbortOnError(abort=True):
+  """Sets behavior of RunCommand to throw AbortError if command process returns 
+  a negative error code"""
+  global _abort_on_error
+  _abort_on_error = abort
+  
+def RunCommand(cmd, timeout_time=None, retry_count=3, return_output=True):
+  """Spawns a subprocess to run the given shell command, and checks for
+  timeout_time. If return_output is True, the output of the command is returned
+  as a string. Otherwise, output of command directed to stdout """
+
+  result = None
+  while True:
+    try:
+      result = RunOnce(cmd, timeout_time=timeout_time, 
+                       return_output=return_output)
+    except errors.WaitForResponseTimedOutError:
+      if retry_count == 0:
+        raise
+      retry_count -= 1
+      logger.Log("No response for %s, retrying" % cmd)
+    else:
+      # Success
+      return result
+
+def RunOnce(cmd, timeout_time=None, return_output=True):
+  start_time = time.time()
+  so = []
+  pid = []
+  global _abort_on_error
+  error_occurred = False
+  
+  def Run():
+    if return_output:
+      output_dest = subprocess.PIPE
+    else:
+      # None means direct to stdout
+      output_dest = None  
+    pipe = subprocess.Popen(
+        cmd,
+        executable='/bin/bash',
+        stdout=output_dest,
+        stderr=subprocess.STDOUT,
+        shell=True)
+    pid.append(pipe.pid)
+    try:
+      output = pipe.communicate()[0]
+      if output is not None and len(output) > 0:
+        so.append(output)
+    except OSError, e:
+      logger.SilentLog("failed to retrieve stdout from: %s" % cmd)
+      logger.Log(e)
+      so.append("ERROR")
+      error_occurred = True
+    if pipe.returncode < 0:
+      logger.SilentLog("Error: %s was terminated by signal %d" %(cmd, 
+          pipe.returncode))
+      error_occurred = True  
+
+  t = threading.Thread(target=Run)
+  t.start()
+
+  break_loop = False
+  while not break_loop:
+    if not t.isAlive():
+      break_loop = True
+
+    # Check the timeout
+    if (not break_loop and timeout_time is not None
+        and time.time() > start_time + timeout_time):
+      try:
+        os.kill(pid[0], signal.SIGKILL)
+      except OSError:
+        # process already dead. No action required.
+        pass
+
+      logger.SilentLog("about to raise a timeout for: %s" % cmd)
+      raise errors.WaitForResponseTimedOutError
+    if not break_loop:
+      time.sleep(0.1)
+
+  t.join()
+
+  if _abort_on_error and error_occurred:
+    raise errors.AbortError
+  
+  return "".join(so)
diff --git a/testrunner/test_defs.py b/testrunner/test_defs.py
index 6039e25..949ad6e 100644
--- a/testrunner/test_defs.py
+++ b/testrunner/test_defs.py
@@ -15,173 +15,182 @@
 # See the License for the specific language governing permissions and 
 # limitations under the License.
 
+"""Parser for test definition xml files."""
+
 # Python imports
 import xml.dom.minidom
 import xml.parsers
-from sets import Set
 
 # local imports
-import logger
 import errors
+import logger
+
 
 class TestDefinitions(object):
-  """Accessor for a test definitions xml file
-     Expected format is:
-     <test-definitions>
-        <test 
-           name="" 
-           package=""
-           [runner=""]
-           [class=""]
-           [coverage_target=""]
-           [build_path=""]
-           [continuous]
-         />
-        <test  ...  
-     </test-definitions> 
-     
-     TODO: add format checking
+  """Accessor for a test definitions xml file data.
+
+  Expected format is:
+   <test-definitions>
+     <test
+       name=""
+       package=""
+       [runner=""]
+       [class=""]
+       [coverage_target=""]
+       [build_path=""]
+       [continuous]
+      />
+     <test  ...
+   </test-definitions>
+
+  TODO: add format checking.
   """
 
   # tag/attribute constants
-  _TEST_TAG_NAME = 'test'
+  _TEST_TAG_NAME = "test"
 
-  def __init__(self, ):
+  def __init__(self):
     # dictionary of test name to tests
     self._testname_map = {}
-    
+
   def __iter__(self):
     return iter(self._testname_map.values())
-      
+
   def Parse(self, file_path):
-    """Parse the test suite data from from given file path, and add it to the 
-       current object
-       Args:
-         file_path: absolute file path to parse
-       Raises:
-         errors.ParseError if file_path cannot be parsed  
-      """
+    """Parse the test suite data from from given file path.
+
+    Args:
+      file_path: absolute file path to parse
+    Raises:
+      ParseError if file_path cannot be parsed
+    """
     try:
       doc = xml.dom.minidom.parse(file_path)
     except IOError:
-      logger.Log('test file %s does not exist' % file_path)
+      logger.Log("test file %s does not exist" % file_path)
       raise errors.ParseError
     except xml.parsers.expat.ExpatError:
-      logger.Log('Error Parsing xml file: %s ' %  file_path)
+      logger.Log("Error Parsing xml file: %s " %  file_path)
       raise errors.ParseError
-    return self._ParseDoc(doc)
-  
+    self._ParseDoc(doc)
+
   def ParseString(self, xml_string):
-    """Alternate parse method that accepts a string of the xml data instead of a
-      file
-    """
+    """Alternate parse method that accepts a string of the xml data."""
     doc = xml.dom.minidom.parseString(xml_string)
     # TODO: catch exceptions and raise ParseError
-    return self._ParseDoc(doc)  
+    return self._ParseDoc(doc)
 
-  def _ParseDoc(self, doc):    
+  def _ParseDoc(self, doc):
     suite_elements = doc.getElementsByTagName(self._TEST_TAG_NAME)
 
     for suite_element in suite_elements:
       test = self._ParseTestSuite(suite_element)
       self._AddTest(test)
-  
+
   def _ParseTestSuite(self, suite_element):
-    """Parse the suite element
-       Returns a TestSuite object, populated with parsed data 
-    """   
+    """Parse the suite element.
+    
+    Returns:
+      a TestSuite object, populated with parsed data
+    """
     test = TestSuite(suite_element)
-    return test    
-    
+    return test
+
   def _AddTest(self, test):
-    """ Adds a test to this TestManifest. If a test already exists with the
-      same name, it overrides it"""  
-    self._testname_map[test.GetName()] = test
+    """Adds a test to this TestManifest.
     
+    If a test already exists with the same name, it overrides it.
+    
+    Args:
+      test: TestSuite to add
+    """
+    self._testname_map[test.GetName()] = test
+
   def GetTests(self):
     return self._testname_map.values()
-  
+
   def GetContinuousTests(self):
     con_tests = []
     for test in self.GetTests():
       if test.IsContinuous():
         con_tests.append(test)
-    return con_tests    
-  
+    return con_tests
+
   def GetTest(self, name):
-    try:
-      return self._testname_map[name]
-    except KeyError:
-      return None
+    return self._testname_map.get(name, None)
   
-class TestSuite:
-  """ Represents one test suite definition parsed from xml """
-  
-  _NAME_ATTR = 'name'
-  _PKG_ATTR = 'package'
-  _RUNNER_ATTR = 'runner'
-  _CLASS_ATTR = 'class'
-  _TARGET_ATTR = 'coverage_target'
-  _BUILD_ATTR = 'build_path'
-  _CONTINUOUS_ATTR = 'continuous'
-  
-  _DEFAULT_RUNNER = 'android.test.InstrumentationTestRunner'
-  
+class TestSuite(object):
+  """Represents one test suite definition parsed from xml."""
+
+  _NAME_ATTR = "name"
+  _PKG_ATTR = "package"
+  _RUNNER_ATTR = "runner"
+  _CLASS_ATTR = "class"
+  _TARGET_ATTR = "coverage_target"
+  _BUILD_ATTR = "build_path"
+  _CONTINUOUS_ATTR = "continuous"
+
+  _DEFAULT_RUNNER = "android.test.InstrumentationTestRunner"
+
   def __init__(self, suite_element):
-    """ Populates this instance's data from given suite xml element"""
+    """Populates this instance's data from given suite xml element."""
     self._name = suite_element.getAttribute(self._NAME_ATTR)
     self._package = suite_element.getAttribute(self._PKG_ATTR)
     if suite_element.hasAttribute(self._RUNNER_ATTR):
       self._runner = suite_element.getAttribute(self._RUNNER_ATTR)
     else:
-      self._runner = self._DEFAULT_RUNNER 
+      self._runner = self._DEFAULT_RUNNER
     if suite_element.hasAttribute(self._CLASS_ATTR):
       self._class = suite_element.getAttribute(self._CLASS_ATTR)
     else:
-      self._class = None  
-    if suite_element.hasAttribute(self._TARGET_ATTR):  
+      self._class = None
+    if suite_element.hasAttribute(self._TARGET_ATTR):
       self._target_name = suite_element.getAttribute(self._TARGET_ATTR)
     else:
       self._target_name = None
-    if suite_element.hasAttribute(self._BUILD_ATTR):  
+    if suite_element.hasAttribute(self._BUILD_ATTR):
       self._build_path = suite_element.getAttribute(self._BUILD_ATTR)
     else:
       self._build_path = None
-    if suite_element.hasAttribute(self._CONTINUOUS_ATTR):  
+    if suite_element.hasAttribute(self._CONTINUOUS_ATTR):
       self._continuous = suite_element.getAttribute(self._CONTINUOUS_ATTR)
     else:
       self._continuous = False
-      
+
   def GetName(self):
     return self._name
-  
+
   def GetPackageName(self):
     return self._package
 
   def GetRunnerName(self):
     return self._runner
-  
+
   def GetClassName(self):
     return self._class
-  
+
   def GetTargetName(self):
-    """ Retrieve module that this test is targeting - used to show code coverage
+    """Retrieve module that this test is targeting.
+    
+    Used for generating code coverage metrics.
     """ 
     return self._target_name
-  
+
   def GetBuildPath(self):
-    """ Return the path, relative to device root, of this test's Android.mk file
-    """
+    """Returns the build path of this test, relative to source tree root."""
     return self._build_path
 
   def IsContinuous(self):
-    """Returns true if test is flagged as continuous worthy"""  
+    """Returns true if test is flagged as being part of the continuous tests"""  
     return self._continuous
-  
+
 def Parse(file_path):
-  """parses out a TestDefinitions from given path to xml file
+  """Parses out a TestDefinitions from given path to xml file.
+
   Args:
     file_path: string absolute file path
+  Returns:
+    a TestDefinitions object containing data parsed from file_path  
   Raises:
     ParseError if xml format is not recognized
   """
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java
index b1b971d..6ede10d 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java
@@ -215,6 +215,11 @@
         final boolean[] result = new boolean[1];
         try {
             workbench.getProgressService().busyCursorWhile(new IRunnableWithProgress() {
+                /**
+                 * Run the export.
+                 * @throws InvocationTargetException
+                 * @throws InterruptedException
+                 */
                 public void run(IProgressMonitor monitor) throws InvocationTargetException,
                         InterruptedException {
                     try {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java
index 8a9703d..7fd76e9 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java
@@ -397,8 +397,10 @@
     /**
      * Creates the list of destination filenames based on the content of the destination field
      * and the list of APK configurations for the project.
-     * @param file
-     * @return
+     * 
+     * @param file File name from the destination field
+     * @return A list of destination filenames based <code>file</code> and the list of APK
+     *         configurations for the project.
      */
     private Map<String, String[]> getApkFileMap(File file) {
         String filename = file.getName();