Update from https://crrev.com/304121

Includes DEPS updates and port of
https://codereview.chromium.org/665223004 to accomodate skia API change
on android.

Review URL: https://codereview.chromium.org/723343002
diff --git a/build/android/findbugs_filter/findbugs_known_bugs.txt b/build/android/findbugs_filter/findbugs_known_bugs.txt
index f641e15..ee27544 100644
--- a/build/android/findbugs_filter/findbugs_known_bugs.txt
+++ b/build/android/findbugs_filter/findbugs_known_bugs.txt
@@ -24,3 +24,4 @@
 M V EI: org.chromium.content_public.browser.LoadUrlParams.getPostData() may expose internal representation by returning LoadUrlParams.mPostData  At LoadUrlParams.java
 M D NP: Read of unwritten public or protected field data in org.chromium.components.devtools_bridge.SessionDependencyFactory$DataChannelObserverAdapter.onMessage(DataChannel$Buffer)  At SessionDependencyFactory.java
 M D NP: Read of unwritten public or protected field mandatory in org.chromium.components.devtools_bridge.SessionDependencyFactory.createPeerConnection(RTCConfiguration, AbstractPeerConnection$Observer)  At SessionDependencyFactory.java
+M V EI2: org.chromium.net.ChromiumUrlRequest.setUploadData(String, byte[]) may expose internal representation by storing an externally mutable object into ChromiumUrlRequest.mUploadData  At ChromiumUrlRequest.java
diff --git a/build/android/gyp/java_cpp_enum.py b/build/android/gyp/java_cpp_enum.py
index 8ae5f36..b10e7c5 100755
--- a/build/android/gyp/java_cpp_enum.py
+++ b/build/android/gyp/java_cpp_enum.py
@@ -13,14 +13,21 @@
 
 from util import build_utils
 
+# List of C++ types that are compatible with the Java code generated by this
+# script.
+ENUM_FIXED_TYPE_WHITELIST = ['char', 'unsigned char',
+  'short', 'unsigned short',
+  'int', 'int8_t', 'int16_t', 'int32_t', 'uint8_t', 'uint16_t']
+
 class EnumDefinition(object):
   def __init__(self, original_enum_name=None, class_name_override=None,
-               enum_package=None, entries=None):
+               enum_package=None, entries=None, fixed_type=None):
     self.original_enum_name = original_enum_name
     self.class_name_override = class_name_override
     self.enum_package = enum_package
     self.entries = collections.OrderedDict(entries or [])
     self.prefix_to_strip = None
+    self.fixed_type = fixed_type
 
   def AppendEntry(self, key, value):
     if key in self.entries:
@@ -40,6 +47,9 @@
     assert self.class_name
     assert self.enum_package
     assert self.entries
+    if self.fixed_type and self.fixed_type not in ENUM_FIXED_TYPE_WHITELIST:
+      raise Exception('Fixed type %s for enum %s not whitelisted.' %
+          (self.fixed_type, self.class_name))
 
   def _AssignEntryIndices(self):
     # Enums, if given no value, are given the value of the previous enum + 1.
@@ -110,12 +120,17 @@
 class HeaderParser(object):
   single_line_comment_re = re.compile(r'\s*//')
   multi_line_comment_start_re = re.compile(r'\s*/\*')
-  enum_start_re = re.compile(r'^\s*enum\s+(\w+)\s+{\s*$')
   enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?')
   enum_end_re = re.compile(r'^\s*}\s*;\.*$')
   generator_directive_re = re.compile(
       r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$')
 
+  optional_class_or_struct_re = r'(class|struct)?'
+  enum_name_re = r'(\w+)'
+  optional_fixed_type_re = r'(\:\s*(\w+\s*\w+?))?'
+  enum_start_re = re.compile(r'^\s*enum\s+' + optional_class_or_struct_re +
+      '\s*' + enum_name_re + '\s*' + optional_fixed_type_re + '\s*{\s*$')
+
   def __init__(self, lines):
     self._lines = lines
     self._enum_definitions = []
@@ -162,7 +177,8 @@
       if self._generator_directives.empty:
         return
       self._current_definition = EnumDefinition(
-          original_enum_name=enum_start.groups()[0])
+          original_enum_name=enum_start.groups()[1],
+          fixed_type=enum_start.groups()[3])
       self._in_enum = True
     elif generator_directive:
       directive_name = generator_directive.groups()[0]
diff --git a/build/android/gyp/java_cpp_enum_tests.py b/build/android/gyp/java_cpp_enum_tests.py
index bb8150d..3aa386e 100755
--- a/build/android/gyp/java_cpp_enum_tests.py
+++ b/build/android/gyp/java_cpp_enum_tests.py
@@ -14,7 +14,8 @@
 import sys
 import unittest
 
-from java_cpp_enum import EnumDefinition, GenerateOutput, HeaderParser
+from java_cpp_enum import EnumDefinition, GenerateOutput, GetScriptName
+from java_cpp_enum import HeaderParser
 
 sys.path.append(os.path.join(os.path.dirname(__file__), "gyp"))
 from util import build_utils
@@ -31,7 +32,7 @@
 // found in the LICENSE file.
 
 // This file is autogenerated by
-//     build/android/gyp/java_cpp_enum_tests.py
+//     %s
 // From
 //     path/to/file
 
@@ -42,7 +43,7 @@
   public static final int E2 = 2 << 2;
 }
 """
-    self.assertEqual(expected, output)
+    self.assertEqual(expected % GetScriptName(), output)
 
   def testParseSimpleEnum(self):
     test_data = """
@@ -150,6 +151,78 @@
     with self.assertRaises(Exception):
       HeaderParser(test_data).ParseDefinitions()
 
+  def testParseEnumClass(self):
+    test_data = """
+      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+      enum class Foo {
+        FOO_A,
+      };
+    """.split('\n')
+    definitions = HeaderParser(test_data).ParseDefinitions()
+    self.assertEqual(1, len(definitions))
+    definition = definitions[0]
+    self.assertEqual('Foo', definition.class_name)
+    self.assertEqual('test.namespace', definition.enum_package)
+    self.assertEqual(collections.OrderedDict([('A', 0)]),
+                     definition.entries)
+
+  def testParseEnumStruct(self):
+    test_data = """
+      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+      enum struct Foo {
+        FOO_A,
+      };
+    """.split('\n')
+    definitions = HeaderParser(test_data).ParseDefinitions()
+    self.assertEqual(1, len(definitions))
+    definition = definitions[0]
+    self.assertEqual('Foo', definition.class_name)
+    self.assertEqual('test.namespace', definition.enum_package)
+    self.assertEqual(collections.OrderedDict([('A', 0)]),
+                     definition.entries)
+
+  def testParseFixedTypeEnum(self):
+    test_data = """
+      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+      enum Foo : int {
+        FOO_A,
+      };
+    """.split('\n')
+    definitions = HeaderParser(test_data).ParseDefinitions()
+    self.assertEqual(1, len(definitions))
+    definition = definitions[0]
+    self.assertEqual('Foo', definition.class_name)
+    self.assertEqual('test.namespace', definition.enum_package)
+    self.assertEqual('int', definition.fixed_type)
+    self.assertEqual(collections.OrderedDict([('A', 0)]),
+                     definition.entries)
+
+  def testParseFixedTypeEnumClass(self):
+    test_data = """
+      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+      enum class Foo: unsigned short {
+        FOO_A,
+      };
+    """.split('\n')
+    definitions = HeaderParser(test_data).ParseDefinitions()
+    self.assertEqual(1, len(definitions))
+    definition = definitions[0]
+    self.assertEqual('Foo', definition.class_name)
+    self.assertEqual('test.namespace', definition.enum_package)
+    self.assertEqual('unsigned short', definition.fixed_type)
+    self.assertEqual(collections.OrderedDict([('A', 0)]),
+                     definition.entries)
+
+  def testParseUnknownFixedTypeRaises(self):
+    test_data = """
+      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
+      enum class Foo: foo_type {
+        FOO_A,
+      };
+    """.split('\n')
+    with self.assertRaises(Exception):
+      HeaderParser(test_data).ParseDefinitions()
+
   def testEnumValueAssignmentNoneDefined(self):
     definition = EnumDefinition(original_enum_name='c', enum_package='p')
     definition.AppendEntry('A', None)
diff --git a/build/android/gyp/package_resources.py b/build/android/gyp/package_resources.py
index 6444ed9..9b6ec68 100755
--- a/build/android/gyp/package_resources.py
+++ b/build/android/gyp/package_resources.py
@@ -37,6 +37,11 @@
   parser.add_option('--android-manifest', help='AndroidManifest.xml path')
   parser.add_option('--version-code', help='Version code for apk.')
   parser.add_option('--version-name', help='Version name for apk.')
+  parser.add_option(
+      '--shared-resources',
+      action='store_true',
+      help='Make a resource package that can be loaded by a different'
+      'application at runtime to access the package\'s resources.')
   parser.add_option('--resource-zips',
                     help='zip files containing resources to be packaged')
   parser.add_option('--asset-dir',
@@ -132,6 +137,8 @@
     if options.no_compress:
       for ext in options.no_compress.split(','):
         package_command += ['-0', ext]
+    if options.shared_resources:
+      package_command.append('--shared-lib')
 
     if os.path.exists(options.asset_dir):
       package_command += ['-A', options.asset_dir]
diff --git a/build/android/gyp/process_resources.py b/build/android/gyp/process_resources.py
index 6bf71f3..45b0947 100755
--- a/build/android/gyp/process_resources.py
+++ b/build/android/gyp/process_resources.py
@@ -38,6 +38,11 @@
 
   parser.add_option('--android-manifest', help='AndroidManifest.xml path')
   parser.add_option('--custom-package', help='Java package for R.java')
+  parser.add_option(
+      '--shared-resources',
+      action='store_true',
+      help='Make a resource package that can be loaded by a different'
+      'application at runtime to access the package\'s resources.')
 
   parser.add_option('--resource-dirs',
                     help='Directories containing resources of this target.')
@@ -236,6 +241,8 @@
       package_command += ['--custom-package', options.custom_package]
     if options.proguard_file:
       package_command += ['-G', options.proguard_file]
+    if options.shared_resources:
+      package_command.append('--shared-lib')
     build_utils.CheckOutput(package_command, print_stderr=False)
 
     if options.extra_res_packages:
diff --git a/build/android/pylib/base/base_test_result.py b/build/android/pylib/base/base_test_result.py
index 1f45214..f9e61a9 100644
--- a/build/android/pylib/base/base_test_result.py
+++ b/build/android/pylib/base/base_test_result.py
@@ -23,18 +23,20 @@
 class BaseTestResult(object):
   """Base class for a single test result."""
 
-  def __init__(self, name, test_type, log=''):
+  def __init__(self, name, test_type, duration=0, log=''):
     """Construct a BaseTestResult.
 
     Args:
       name: Name of the test which defines uniqueness.
       test_type: Type of the test result as defined in ResultType.
+      duration: Time it took for the test to run in milliseconds.
       log: An optional string listing any errors.
     """
     assert name
     assert test_type in ResultType.GetTypes()
     self._name = name
     self._test_type = test_type
+    self._duration = duration
     self._log = log
 
   def __str__(self):
@@ -66,6 +68,10 @@
     """Get the test result type."""
     return self._test_type
 
+  def GetDuration(self):
+    """Get the test duration."""
+    return self._duration
+
   def GetLog(self):
     """Get the test log."""
     return self._log
diff --git a/build/android/pylib/base/base_test_runner.py b/build/android/pylib/base/base_test_runner.py
index 1f76149..6e51b43 100644
--- a/build/android/pylib/base/base_test_runner.py
+++ b/build/android/pylib/base/base_test_runner.py
@@ -9,10 +9,8 @@
 # model.
 
 import logging
-import time
 
 from pylib import ports
-from pylib.chrome_test_server_spawner import SpawningServer
 from pylib.device import device_utils
 from pylib.forwarder import Forwarder
 from pylib.valgrind_tools import CreateTool
@@ -42,7 +40,6 @@
     self._forwarder_device_port = 8000
     self.forwarder_base_url = ('http://localhost:%d' %
         self._forwarder_device_port)
-    self._spawning_server = None
     # We will allocate port for test server spawner when calling method
     # LaunchChromeTestServerSpawner and allocate port for test server when
     # starting it in TestServerThread.
@@ -146,45 +143,4 @@
     if self._http_server:
       self._UnmapPorts([(self._forwarder_device_port, self._http_server.port)])
       self._http_server.ShutdownHttpServer()
-    if self._spawning_server:
-      self._spawning_server.Stop()
 
-  def CleanupSpawningServerState(self):
-    """Tells the spawning server to clean up any state.
-
-    If the spawning server is reused for multiple tests, this should be called
-    after each test to prevent tests affecting each other.
-    """
-    if self._spawning_server:
-      self._spawning_server.CleanupState()
-
-  def LaunchChromeTestServerSpawner(self):
-    """Launches test server spawner."""
-    server_ready = False
-    error_msgs = []
-    # TODO(pliard): deflake this function. The for loop should be removed as
-    # well as IsHttpServerConnectable(). spawning_server.Start() should also
-    # block until the server is ready.
-    # Try 3 times to launch test spawner server.
-    for _ in xrange(0, 3):
-      self.test_server_spawner_port = ports.AllocateTestServerPort()
-      self._ForwardPorts(
-          [(self.test_server_spawner_port, self.test_server_spawner_port)])
-      self._spawning_server = SpawningServer(self.test_server_spawner_port,
-                                             self.device,
-                                             self.tool)
-      self._spawning_server.Start()
-      server_ready, error_msg = ports.IsHttpServerConnectable(
-          '127.0.0.1', self.test_server_spawner_port, path='/ping',
-          expected_read='ready')
-      if server_ready:
-        break
-      else:
-        error_msgs.append(error_msg)
-      self._spawning_server.Stop()
-      # Wait for 2 seconds then restart.
-      time.sleep(2)
-    if not server_ready:
-      logging.error(';'.join(error_msgs))
-      raise Exception('Can not start the test spawner server.')
-    self._PushTestServerPortInfoToDevice()
diff --git a/build/android/pylib/base/test_collection.py b/build/android/pylib/base/test_collection.py
new file mode 100644
index 0000000..e914652
--- /dev/null
+++ b/build/android/pylib/base/test_collection.py
@@ -0,0 +1,80 @@
+# Copyright 2013 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 threading
+
+class TestCollection(object):
+  """A threadsafe collection of tests.
+
+  Args:
+    tests: List of tests to put in the collection.
+  """
+
+  def __init__(self, tests=None):
+    if not tests:
+      tests = []
+    self._lock = threading.Lock()
+    self._tests = []
+    self._tests_in_progress = 0
+    # Used to signal that an item is available or all items have been handled.
+    self._item_available_or_all_done = threading.Event()
+    for t in tests:
+      self.add(t)
+
+  def _pop(self):
+    """Pop a test from the collection.
+
+    Waits until a test is available or all tests have been handled.
+
+    Returns:
+      A test or None if all tests have been handled.
+    """
+    while True:
+      # Wait for a test to be available or all tests to have been handled.
+      self._item_available_or_all_done.wait()
+      with self._lock:
+        # Check which of the two conditions triggered the signal.
+        if self._tests_in_progress == 0:
+          return None
+        try:
+          return self._tests.pop(0)
+        except IndexError:
+          # Another thread beat us to the available test, wait again.
+          self._item_available_or_all_done.clear()
+
+  def add(self, test):
+    """Add an test to the collection.
+
+    Args:
+      test: A test to add.
+    """
+    with self._lock:
+      self._tests.append(test)
+      self._item_available_or_all_done.set()
+      self._tests_in_progress += 1
+
+  def test_completed(self):
+    """Indicate that a test has been fully handled."""
+    with self._lock:
+      self._tests_in_progress -= 1
+      if self._tests_in_progress == 0:
+        # All tests have been handled, signal all waiting threads.
+        self._item_available_or_all_done.set()
+
+  def __iter__(self):
+    """Iterate through tests in the collection until all have been handled."""
+    while True:
+      r = self._pop()
+      if r is None:
+        break
+      yield r
+
+  def __len__(self):
+    """Return the number of tests currently in the collection."""
+    return len(self._tests)
+
+  def test_names(self):
+    """Return a list of the names of the tests currently in the collection."""
+    with self._lock:
+      return list(t.test for t in self._tests)
diff --git a/build/android/pylib/base/test_dispatcher.py b/build/android/pylib/base/test_dispatcher.py
index 7b00ccd..929c408 100644
--- a/build/android/pylib/base/test_dispatcher.py
+++ b/build/android/pylib/base/test_dispatcher.py
@@ -24,6 +24,7 @@
 from pylib import android_commands
 from pylib import constants
 from pylib.base import base_test_result
+from pylib.base import test_collection
 from pylib.device import device_errors
 from pylib.utils import reraiser_thread
 from pylib.utils import watchdog_timer
@@ -65,92 +66,16 @@
     self.tries = tries
 
 
-class _TestCollection(object):
-  """A threadsafe collection of tests.
-
-  Args:
-    tests: List of tests to put in the collection.
-  """
-
-  def __init__(self, tests=None):
-    if not tests:
-      tests = []
-    self._lock = threading.Lock()
-    self._tests = []
-    self._tests_in_progress = 0
-    # Used to signal that an item is available or all items have been handled.
-    self._item_available_or_all_done = threading.Event()
-    for t in tests:
-      self.add(t)
-
-  def _pop(self):
-    """Pop a test from the collection.
-
-    Waits until a test is available or all tests have been handled.
-
-    Returns:
-      A test or None if all tests have been handled.
-    """
-    while True:
-      # Wait for a test to be available or all tests to have been handled.
-      self._item_available_or_all_done.wait()
-      with self._lock:
-        # Check which of the two conditions triggered the signal.
-        if self._tests_in_progress == 0:
-          return None
-        try:
-          return self._tests.pop(0)
-        except IndexError:
-          # Another thread beat us to the available test, wait again.
-          self._item_available_or_all_done.clear()
-
-  def add(self, test):
-    """Add an test to the collection.
-
-    Args:
-      test: A test to add.
-    """
-    with self._lock:
-      self._tests.append(test)
-      self._item_available_or_all_done.set()
-      self._tests_in_progress += 1
-
-  def test_completed(self):
-    """Indicate that a test has been fully handled."""
-    with self._lock:
-      self._tests_in_progress -= 1
-      if self._tests_in_progress == 0:
-        # All tests have been handled, signal all waiting threads.
-        self._item_available_or_all_done.set()
-
-  def __iter__(self):
-    """Iterate through tests in the collection until all have been handled."""
-    while True:
-      r = self._pop()
-      if r is None:
-        break
-      yield r
-
-  def __len__(self):
-    """Return the number of tests currently in the collection."""
-    return len(self._tests)
-
-  def test_names(self):
-    """Return a list of the names of the tests currently in the collection."""
-    with self._lock:
-      return list(t.test for t in self._tests)
-
-
-def _RunTestsFromQueue(runner, test_collection, out_results, watcher,
+def _RunTestsFromQueue(runner, collection, out_results, watcher,
                        num_retries, tag_results_with_device=False):
-  """Runs tests from the test_collection until empty using the given runner.
+  """Runs tests from the collection until empty using the given runner.
 
   Adds TestRunResults objects to the out_results list and may add tests to the
   out_retry list.
 
   Args:
     runner: A TestRunner object used to run the tests.
-    test_collection: A _TestCollection from which to get _Test objects to run.
+    collection: A TestCollection from which to get _Test objects to run.
     out_results: A list to add TestRunResults to.
     watcher: A watchdog_timer.WatchdogTimer object, used as a shared timeout.
     num_retries: Number of retries for a test.
@@ -174,7 +99,7 @@
       new_test_run_results.AddResult(test_result)
     return new_test_run_results
 
-  for test in test_collection:
+  for test in collection:
     watcher.Reset()
     try:
       if runner.device_serial not in android_commands.GetAttachedDevices():
@@ -192,18 +117,18 @@
         pass_results.AddResults(result.GetPass())
         out_results.append(pass_results)
         logging.warning('Will retry test, try #%s.' % test.tries)
-        test_collection.add(_Test(test=retry, tries=test.tries))
+        collection.add(_Test(test=retry, tries=test.tries))
       else:
         # All tests passed or retry limit reached. Either way, record results.
         out_results.append(result)
     except:
       # An unhandleable exception, ensure tests get run by another device and
       # reraise this exception on the main thread.
-      test_collection.add(test)
+      collection.add(test)
       raise
     finally:
       # Retries count as separate tasks so always mark the popped test as done.
-      test_collection.test_completed()
+      collection.test_completed()
 
 
 def _SetUp(runner_factory, device, out_runners, threadsafe_counter):
@@ -238,7 +163,7 @@
 
   Args:
     runners: A list of TestRunner objects.
-    test_collection_factory: A callable to generate a _TestCollection object for
+    test_collection_factory: A callable to generate a TestCollection object for
         each test runner.
     num_retries: Number of retries for a test.
     timeout: Watchdog timeout in seconds.
@@ -384,16 +309,17 @@
 
   tests_expanded = ApplyMaxPerRun(tests, max_per_run)
   if shard:
-    # Generate a shared _TestCollection object for all test runners, so they
+    # Generate a shared TestCollection object for all test runners, so they
     # draw from a common pool of tests.
-    shared_test_collection = _TestCollection([_Test(t) for t in tests_expanded])
+    shared_test_collection = test_collection.TestCollection(
+        [_Test(t) for t in tests_expanded])
     test_collection_factory = lambda: shared_test_collection
     tag_results_with_device = False
     log_string = 'sharded across devices'
   else:
-    # Generate a unique _TestCollection object for each test runner, but use
+    # Generate a unique TestCollection object for each test runner, but use
     # the same set of tests.
-    test_collection_factory = lambda: _TestCollection(
+    test_collection_factory = lambda: test_collection.TestCollection(
         [_Test(t) for t in tests_expanded])
     tag_results_with_device = True
     log_string = 'replicated on each device'
diff --git a/build/android/pylib/base/test_dispatcher_unittest.py b/build/android/pylib/base/test_dispatcher_unittest.py
index d349f32..b57cca9 100644
--- a/build/android/pylib/base/test_dispatcher_unittest.py
+++ b/build/android/pylib/base/test_dispatcher_unittest.py
@@ -18,6 +18,7 @@
 android_commands.GetAttachedDevices = lambda: ['0', '1']
 from pylib import constants
 from pylib.base import base_test_result
+from pylib.base import test_collection
 from pylib.base import test_dispatcher
 from pylib.utils import watchdog_timer
 
@@ -83,7 +84,7 @@
   @staticmethod
   def _RunTests(mock_runner, tests):
     results = []
-    tests = test_dispatcher._TestCollection(
+    tests = test_collection.TestCollection(
         [test_dispatcher._Test(t) for t in tests])
     test_dispatcher._RunTestsFromQueue(mock_runner, tests, results,
                                        watchdog_timer.WatchdogTimer(None), 2)
@@ -129,7 +130,7 @@
   """Tests test_dispatcher._RunAllTests and test_dispatcher._CreateRunners."""
   def setUp(self):
     self.tests = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
-    shared_test_collection = test_dispatcher._TestCollection(
+    shared_test_collection = test_collection.TestCollection(
         [test_dispatcher._Test(t) for t in self.tests])
     self.test_collection_factory = lambda: shared_test_collection
 
diff --git a/build/android/pylib/base/test_server.py b/build/android/pylib/base/test_server.py
new file mode 100644
index 0000000..085a51e
--- /dev/null
+++ b/build/android/pylib/base/test_server.py
@@ -0,0 +1,19 @@
+# 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.
+
+class TestServer(object):
+  """Base class for any server that needs to be set up for the tests."""
+
+  def __init__(self, *args, **kwargs):
+    pass
+
+  def SetUp(self):
+    raise NotImplementedError
+
+  def Reset(self):
+    raise NotImplementedError
+
+  def TearDown(self):
+    raise NotImplementedError
+
diff --git a/build/android/pylib/constants.py b/build/android/pylib/constants.py
index cef098f..29da601 100644
--- a/build/android/pylib/constants.py
+++ b/build/android/pylib/constants.py
@@ -189,13 +189,12 @@
       'pylib.device.device_utils_test',
     ]
   },
-# TODO(mkosiba) Enable after fixing these tests.
-# 'gyp_py_unittests': {
-#   'path': os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android', 'gyp'),
-#   'test_modules': [
-#     'java_cpp_enum_tests'
-#   ]
-# },
+  'gyp_py_unittests': {
+    'path': os.path.join(DIR_SOURCE_ROOT, 'build', 'android', 'gyp'),
+    'test_modules': [
+      'java_cpp_enum_tests',
+    ]
+  },
 }
 
 LOCAL_MACHINE_TESTS = ['junit', 'python']
@@ -217,6 +216,10 @@
   os.environ['CHROMIUM_OUT_DIR'] = build_directory
 
 
+def SetOutputDirectort(output_directory):
+  os.environ['CHROMIUM_OUTPUT_DIR'] = output_directory
+
+
 def GetOutDirectory(build_type=None):
   """Returns the out directory where the output binaries are built.
 
@@ -224,6 +227,10 @@
     build_type: Build type, generally 'Debug' or 'Release'. Defaults to the
       globally set build type environment variable BUILDTYPE.
   """
+  if 'CHROMIUM_OUTPUT_DIR' in os.environ:
+    return os.path.abspath(os.path.join(
+        DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUTPUT_DIR')))
+
   return os.path.abspath(os.path.join(
       DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUT_DIR', 'out'),
       GetBuildType() if build_type is None else build_type))
diff --git a/build/android/pylib/device/adb_wrapper.py b/build/android/pylib/device/adb_wrapper.py
index 97f387a..e7a8418 100644
--- a/build/android/pylib/device/adb_wrapper.py
+++ b/build/android/pylib/device/adb_wrapper.py
@@ -152,14 +152,14 @@
     self._DeviceAdbCmd(['pull', remote, local], timeout, retries)
     _VerifyLocalFileExists(local)
 
-  def Shell(self, command, expect_rc=None, timeout=_DEFAULT_TIMEOUT,
+  def Shell(self, command, expect_rc=0, timeout=_DEFAULT_TIMEOUT,
             retries=_DEFAULT_RETRIES):
     """Runs a shell command on the device.
 
     Args:
       command: The shell command to run.
-      expect_rc: (optional) If set checks that the command's return code matches
-        this value.
+      expect_rc: (optional) Check that the command's return code matches this
+        value. Default is 0. If set to None the test is skipped.
       timeout: (optional) Timeout per try in seconds.
       retries: (optional) Number of retries to attempt.
 
diff --git a/build/android/pylib/device/device_utils.py b/build/android/pylib/device/device_utils.py
index f4bce41..f6bebce 100644
--- a/build/android/pylib/device/device_utils.py
+++ b/build/android/pylib/device/device_utils.py
@@ -215,9 +215,6 @@
       CommandTimeoutError on timeout.
       DeviceUnreachableError on missing device.
     """
-    return self._GetExternalStoragePathImpl()
-
-  def _GetExternalStoragePathImpl(self):
     if 'external_storage' in self._cache:
       return self._cache['external_storage']
 
@@ -285,7 +282,8 @@
       return self.GetProp('sys.boot_completed') == '1'
 
     def wifi_enabled():
-      return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'])
+      return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'],
+                                                        check_return=False)
 
     self.adb.WaitForDevice()
     timeout_retry.WaitFor(sd_card_ready)
@@ -442,7 +440,7 @@
       timeout = self._default_timeout
 
     try:
-      output = self.adb.Shell(cmd, expect_rc=0)
+      output = self.adb.Shell(cmd)
     except device_errors.AdbShellCommandFailedError as e:
       if check_return:
         raise
@@ -531,6 +529,22 @@
         raise device_errors.CommandFailedError(l, device=str(self))
 
   @decorators.WithTimeoutAndRetriesFromInstance()
+  def StartInstrumentation(self, component, finish=True, raw=False,
+                           extras=None, timeout=None, retries=None):
+    if extras is None:
+      extras = {}
+
+    cmd = ['am', 'instrument']
+    if finish:
+      cmd.append('-w')
+    if raw:
+      cmd.append('-r')
+    for k, v in extras.iteritems():
+      cmd.extend(['-e', k, v])
+    cmd.append(component)
+    return self.RunShellCommand(cmd, check_return=True)
+
+  @decorators.WithTimeoutAndRetriesFromInstance()
   def BroadcastIntent(self, intent, timeout=None, retries=None):
     """Send a broadcast intent.
 
@@ -768,7 +782,7 @@
       zip_proc.start()
       zip_proc.join()
 
-      zip_on_device = '%s/tmp.zip' % self._GetExternalStoragePathImpl()
+      zip_on_device = '%s/tmp.zip' % self.GetExternalStoragePath()
       try:
         self.adb.Push(zip_file.name, zip_on_device)
         self.RunShellCommand(
diff --git a/build/android/pylib/device/device_utils_test.py b/build/android/pylib/device/device_utils_test.py
index fa4ffaa..65547d4 100755
--- a/build/android/pylib/device/device_utils_test.py
+++ b/build/android/pylib/device/device_utils_test.py
@@ -26,6 +26,7 @@
 from pylib.device import device_errors
 from pylib.device import device_utils
 from pylib.device import intent
+from pylib.utils import mock_calls
 
 # RunCommand from third_party/android_testrunner/run_command.py is mocked
 # below, so its path needs to be in sys.path.
@@ -218,82 +219,7 @@
         '0123456789abcdef', default_timeout=1, default_retries=0)
 
 
-class Args:
-  def __init__(self, *args, **kwargs):
-    self.args = args
-    self.kwargs = kwargs
-
-  def __eq__(self, other):
-    return (self.args, self.kwargs) == (other.args, other.kwargs)
-
-  def __repr__(self):
-    return '%s(%s)' % (type(self).__name__, str(self))
-
-  def __str__(self):
-    toks = (['%r' % v for v in self.args] +
-            ['%s=%r' % (k, self.kwargs[k]) for k in sorted(self.kwargs)])
-    return ', '.join(toks)
-
-
-class MockCallSequence(object):
-  def __init__(self, test_case, obj, method, calls):
-    def assert_and_return(*args, **kwargs):
-      received_args = Args(*args, **kwargs)
-      test_case.assertTrue(
-          self._calls,
-          msg=('Unexpected call\n'
-               '  received: %s(%s)\n' % (self._method, received_args)))
-      expected_args, return_value = self._calls.pop(0)
-      test_case.assertTrue(
-          received_args == expected_args,
-          msg=('Call does not match expected args\n'
-               '  received: %s(%s)\n'
-               '  expected: %s(%s)\n'
-               % (self._method, received_args,
-                  self._method, expected_args)))
-      if isinstance(return_value, Exception):
-        raise return_value
-      else:
-        return return_value
-
-    self._calls = list(calls)
-    self._test_case = test_case
-    self._method = method
-    self._patched = mock.patch.object(obj, self._method,
-                                      side_effect=assert_and_return)
-
-  def __enter__(self):
-    return self._patched.__enter__()
-
-  def __exit__(self, exc_type, exc_val, exc_tb):
-    self._patched.__exit__(exc_type, exc_val, exc_tb)
-    if exc_type is None:
-      missing = ''.join('  expected: %s(%s)\n'
-                        % (self._method, expected_args)
-                        for expected_args, _ in self._calls)
-      self._test_case.assertTrue(
-          not missing,
-          msg=('Expected calls not found\n' + missing))
-
-
-class _ShellError:
-  def __init__(self, output=None, return_code=1):
-    if output is None:
-      self.output = 'Permission denied\r\n'
-    else:
-      self.output = output
-    self.return_code = return_code
-
-
-class _CmdTimeout:
-  def __init__(self, msg=None):
-    if msg is None:
-      self.msg = 'Operation timed out'
-    else:
-      self.msg = msg
-
-
-class DeviceUtilsNewImplTest(unittest.TestCase):
+class DeviceUtilsNewImplTest(mock_calls.TestCase):
 
   def setUp(self):
     test_serial = '0123456789abcdef'
@@ -302,66 +228,52 @@
     self.adb.GetDeviceSerial.return_value = test_serial
     self.device = device_utils.DeviceUtils(
         self.adb, default_timeout=10, default_retries=0)
+    self.watchMethodCalls(self.call.adb)
 
-  def assertShellCallSequence(self, calls):
-    '''Assert that we expect a sequence of calls to adb.Shell.
+  def ShellError(self, output=None, exit_code=1):
+    def action(cmd, *args, **kwargs):
+      raise device_errors.AdbShellCommandFailedError(
+          cmd, exit_code, output, str(self.device))
+    if output is None:
+      output = 'Permission denied\n'
+    return action
 
-    Args:
-      calls: a sequence of (cmd, return_value) pairs, where |cmd| is the
-        expected shell command to run on the device (with any quoting already
-        applied), and |return_value| is either a string to give as mock output
-        or a _ShellError object to raise an AdbShellCommandFailedError.
-    '''
-    def mk_expected_call(cmd, return_value):
-      expected_args = Args(cmd, expect_rc=0)
-      if isinstance(return_value, _ShellError):
-        return_value = device_errors.AdbShellCommandFailedError(cmd,
-            return_value.return_code, return_value.output, str(self.device))
-      elif isinstance(return_value, _CmdTimeout):
-        return_value = device_errors.CommandTimeoutError(return_value.msg,
-                                                         str(self.device))
-      return (expected_args, return_value)
+  def TimeoutError(self, msg=None):
+    if msg is None:
+      msg = 'Operation timed out'
+    return mock.Mock(side_effect=device_errors.CommandTimeoutError(
+        msg, str(self.device)))
 
-    expected_calls = (mk_expected_call(a, r) for a, r in calls)
-    return MockCallSequence(self, self.adb, 'Shell', expected_calls)
-
-  def assertShellCall(self, cmd, return_value=''):
-    return self.assertShellCallSequence([(cmd, return_value)])
-
-
-class DeviceUtilsHybridImplTest(DeviceUtilsOldImplTest):
-
-  def setUp(self):
-    super(DeviceUtilsHybridImplTest, self).setUp()
-    self.device.adb = self.adb = mock.Mock(spec=adb_wrapper.AdbWrapper)
+  def CommandError(self, msg=None):
+    if msg is None:
+      msg = 'Command failed'
+    return mock.Mock(side_effect=device_errors.CommandFailedError(
+        msg, str(self.device)))
 
 
 class DeviceUtilsIsOnlineTest(DeviceUtilsNewImplTest):
 
   def testIsOnline_true(self):
-    self.adb.GetState = mock.Mock(return_value='device')
-    self.assertTrue(self.device.IsOnline())
-    self.adb.GetState.assert_called_once_with()
+    with self.assertCall(self.call.adb.GetState(), 'device'):
+      self.assertTrue(self.device.IsOnline())
 
   def testIsOnline_false(self):
-    self.adb.GetState = mock.Mock(return_value='offline')
-    self.assertFalse(self.device.IsOnline())
-    self.adb.GetState.assert_called_once_with()
+    with self.assertCall(self.call.adb.GetState(), 'offline'):
+      self.assertFalse(self.device.IsOnline())
 
   def testIsOnline_error(self):
-    self.adb.GetState = mock.Mock(
-     side_effect=device_errors.CommandFailedError('falied'))
-    self.assertFalse(self.device.IsOnline())
-    self.adb.GetState.assert_called_once_with()
+    with self.assertCall(self.call.adb.GetState(), self.CommandError()):
+      self.assertFalse(self.device.IsOnline())
+
 
 class DeviceUtilsHasRootTest(DeviceUtilsNewImplTest):
 
   def testHasRoot_true(self):
-    with self.assertShellCall('ls /root', 'foo\r\n'):
+    with self.assertCall(self.call.adb.Shell('ls /root'), 'foo\n'):
       self.assertTrue(self.device.HasRoot())
 
   def testHasRoot_false(self):
-    with self.assertShellCall('ls /root', _ShellError()):
+    with self.assertCall(self.call.adb.Shell('ls /root'), self.ShellError()):
       self.assertFalse(self.device.HasRoot())
 
 
@@ -395,25 +307,26 @@
 class DeviceUtilsIsUserBuildTest(DeviceUtilsNewImplTest):
 
   def testIsUserBuild_yes(self):
-    with self.assertShellCall('getprop ro.build.type', 'user\r\n'):
+    with self.assertCall(
+        self.call.device.GetProp('ro.build.type', cache=True), 'user'):
       self.assertTrue(self.device.IsUserBuild())
 
   def testIsUserBuild_no(self):
-    with self.assertShellCall('getprop ro.build.type', 'userdebug\r\n'):
+    with self.assertCall(
+        self.call.device.GetProp('ro.build.type', cache=True), 'userdebug'):
       self.assertFalse(self.device.IsUserBuild())
 
 
 class DeviceUtilsGetExternalStoragePathTest(DeviceUtilsNewImplTest):
 
   def testGetExternalStoragePath_succeeds(self):
-    fakeStoragePath = '/fake/storage/path'
-    with self.assertShellCall('echo $EXTERNAL_STORAGE',
-                              '%s\r\n' % fakeStoragePath):
-      self.assertEquals(fakeStoragePath,
+    with self.assertCall(
+        self.call.adb.Shell('echo $EXTERNAL_STORAGE'), '/fake/storage/path\n'):
+      self.assertEquals('/fake/storage/path',
                         self.device.GetExternalStoragePath())
 
   def testGetExternalStoragePath_fails(self):
-    with self.assertShellCall('echo $EXTERNAL_STORAGE', '\r\n'):
+    with self.assertCall(self.call.adb.Shell('echo $EXTERNAL_STORAGE'), '\n'):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.GetExternalStoragePath()
 
@@ -421,148 +334,150 @@
 class DeviceUtilsGetApplicationPathTest(DeviceUtilsNewImplTest):
 
   def testGetApplicationPath_exists(self):
-    with self.assertShellCall('pm path android',
-                              'package:/path/to/android.apk\n'):
+    with self.assertCall(self.call.adb.Shell('pm path android'),
+                         'package:/path/to/android.apk\n'):
       self.assertEquals('/path/to/android.apk',
                         self.device.GetApplicationPath('android'))
 
   def testGetApplicationPath_notExists(self):
-    with self.assertShellCall('pm path not.installed.app',
-                              ''):
+    with self.assertCall(self.call.adb.Shell('pm path not.installed.app'), ''):
       self.assertEquals(None,
                         self.device.GetApplicationPath('not.installed.app'))
 
   def testGetApplicationPath_fails(self):
-    with self.assertShellCall('pm path android',
-                              'ERROR. Is package manager running?\n'):
+    with self.assertCall(self.call.adb.Shell('pm path android'),
+        self.CommandError('ERROR. Is package manager running?\n')):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.GetApplicationPath('android')
 
 
+@mock.patch('time.sleep', mock.Mock())
 class DeviceUtilsWaitUntilFullyBootedTest(DeviceUtilsNewImplTest):
 
   def testWaitUntilFullyBooted_succeedsNoWifi(self):
-    with self.assertShellCallSequence([
-        # sc_card_ready
-        ('echo $EXTERNAL_STORAGE', '/fake/storage/path\r\n'),
-        ('test -d /fake/storage/path', ''),
+    with self.assertCalls(
+        self.call.adb.WaitForDevice(),
+        # sd_card_ready
+        (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
         # pm_ready
-        ('pm path android', 'package:this.is.a.test.package\r\n'),
+        (self.call.device.GetApplicationPath('android'),
+         'package:/some/fake/path'),
         # boot_completed
-        ('getprop sys.boot_completed', '1\r\n')]):
+        (self.call.device.GetProp('sys.boot_completed'), '1')):
       self.device.WaitUntilFullyBooted(wifi=False)
-      self.adb.WaitForDevice.assert_called_once_with()
 
   def testWaitUntilFullyBooted_succeedsWithWifi(self):
-    with self.assertShellCallSequence([
-        # sc_card_ready
-        ('echo $EXTERNAL_STORAGE', '/fake/storage/path\r\n'),
-        ('test -d /fake/storage/path', ''),
+    with self.assertCalls(
+        self.call.adb.WaitForDevice(),
+        # sd_card_ready
+        (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
         # pm_ready
-        ('pm path android', 'package:this.is.a.test.package\r\n'),
+        (self.call.device.GetApplicationPath('android'),
+         'package:/some/fake/path'),
         # boot_completed
-        ('getprop sys.boot_completed', '1\r\n'),
+        (self.call.device.GetProp('sys.boot_completed'), '1'),
         # wifi_enabled
-        ('dumpsys wifi', 'stuff\r\nWi-Fi is enabled\r\nmore stuff\r\n')]):
+        (self.call.adb.Shell('dumpsys wifi'),
+         'stuff\nWi-Fi is enabled\nmore stuff\n')):
       self.device.WaitUntilFullyBooted(wifi=True)
-      self.adb.WaitForDevice.assert_called_once_with()
 
   def testWaitUntilFullyBooted_sdCardReadyFails_noPath(self):
-    with self.assertShellCallSequence([
-        # sc_card_ready
-        ('echo $EXTERNAL_STORAGE', '\r\n')]):
+    with self.assertCalls(
+        self.call.adb.WaitForDevice(),
+        # sd_card_ready
+        (self.call.device.GetExternalStoragePath(), self.CommandError())):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.WaitUntilFullyBooted(wifi=False)
 
-  def testWaitUntilFullyBooted_sdCardReadyFails_emptyPath(self):
-    with mock.patch('time.sleep'):
-      with self.assertShellCallSequence([
-          # sc_card_ready
-          ('echo $EXTERNAL_STORAGE', '/fake/storage/path\r\n'),
-          ('test -d /fake/storage/path', _ShellError()),
-          # sc_card_ready
-          ('test -d /fake/storage/path', _ShellError()),
-          # sc_card_ready
-          ('test -d /fake/storage/path', _CmdTimeout())]):
-        with self.assertRaises(device_errors.CommandTimeoutError):
-          self.device.WaitUntilFullyBooted(wifi=False)
+  def testWaitUntilFullyBooted_sdCardReadyFails_notExists(self):
+    with self.assertCalls(
+        self.call.adb.WaitForDevice(),
+        # sd_card_ready
+        (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+        (self.call.adb.Shell('test -d /fake/storage/path'), self.ShellError()),
+        # sd_card_ready
+        (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+        (self.call.adb.Shell('test -d /fake/storage/path'), self.ShellError()),
+        # sd_card_ready
+        (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+        (self.call.adb.Shell('test -d /fake/storage/path'),
+         self.TimeoutError())):
+      with self.assertRaises(device_errors.CommandTimeoutError):
+        self.device.WaitUntilFullyBooted(wifi=False)
 
   def testWaitUntilFullyBooted_devicePmFails(self):
-    with mock.patch('time.sleep'):
-      with self.assertShellCallSequence([
-          # sc_card_ready
-          ('echo $EXTERNAL_STORAGE', '/fake/storage/path\r\n'),
-          ('test -d /fake/storage/path', ''),
-          # pm_ready
-          ('pm path android', 'Error. Is package manager running?\r\n'),
-          # pm_ready
-          ('pm path android', 'Error. Is package manager running?\r\n'),
-          # pm_ready
-          ('pm path android', _CmdTimeout())]):
-        with self.assertRaises(device_errors.CommandTimeoutError):
-          self.device.WaitUntilFullyBooted(wifi=False)
+    with self.assertCalls(
+        self.call.adb.WaitForDevice(),
+        # sd_card_ready
+        (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        # pm_ready
+        (self.call.device.GetApplicationPath('android'), self.CommandError()),
+        # pm_ready
+        (self.call.device.GetApplicationPath('android'), self.CommandError()),
+        # pm_ready
+        (self.call.device.GetApplicationPath('android'), self.TimeoutError())):
+      with self.assertRaises(device_errors.CommandTimeoutError):
+        self.device.WaitUntilFullyBooted(wifi=False)
 
   def testWaitUntilFullyBooted_bootFails(self):
-    with mock.patch('time.sleep'):
-      with self.assertShellCallSequence([
-          # sc_card_ready
-          ('echo $EXTERNAL_STORAGE', '/fake/storage/path\r\n'),
-          ('test -d /fake/storage/path', ''),
-          # pm_ready
-          ('pm path android', 'package:this.is.a.test.package\r\n'),
-          # boot_completed
-          ('getprop sys.boot_completed', '0\r\n'),
-          # boot_completed
-          ('getprop sys.boot_completed', '0\r\n'),
-          # boot_completed
-          ('getprop sys.boot_completed', _CmdTimeout())]):
-        with self.assertRaises(device_errors.CommandTimeoutError):
-          self.device.WaitUntilFullyBooted(wifi=False)
+    with self.assertCalls(
+        self.call.adb.WaitForDevice(),
+        # sd_card_ready
+        (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        # pm_ready
+        (self.call.device.GetApplicationPath('android'),
+         'package:/some/fake/path'),
+        # boot_completed
+        (self.call.device.GetProp('sys.boot_completed'), '0'),
+        # boot_completed
+        (self.call.device.GetProp('sys.boot_completed'), '0'),
+        # boot_completed
+        (self.call.device.GetProp('sys.boot_completed'), self.TimeoutError())):
+      with self.assertRaises(device_errors.CommandTimeoutError):
+        self.device.WaitUntilFullyBooted(wifi=False)
 
   def testWaitUntilFullyBooted_wifiFails(self):
-    with mock.patch('time.sleep'):
-      with self.assertShellCallSequence([
-          # sc_card_ready
-          ('echo $EXTERNAL_STORAGE', '/fake/storage/path\r\n'),
-          ('test -d /fake/storage/path', ''),
-          # pm_ready
-          ('pm path android', 'package:this.is.a.test.package\r\n'),
-          # boot_completed
-          ('getprop sys.boot_completed', '1\r\n'),
-          # wifi_enabled
-          ('dumpsys wifi', 'stuff\r\nmore stuff\r\n'),
-          # wifi_enabled
-          ('dumpsys wifi', 'stuff\r\nmore stuff\r\n'),
-          # wifi_enabled
-          ('dumpsys wifi', _CmdTimeout())]):
-        with self.assertRaises(device_errors.CommandTimeoutError):
-          self.device.WaitUntilFullyBooted(wifi=True)
+    with self.assertCalls(
+        self.call.adb.WaitForDevice(),
+        # sd_card_ready
+        (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
+        (self.call.adb.Shell('test -d /fake/storage/path'), ''),
+        # pm_ready
+        (self.call.device.GetApplicationPath('android'),
+         'package:/some/fake/path'),
+        # boot_completed
+        (self.call.device.GetProp('sys.boot_completed'), '1'),
+        # wifi_enabled
+        (self.call.adb.Shell('dumpsys wifi'), 'stuff\nmore stuff\n'),
+        # wifi_enabled
+        (self.call.adb.Shell('dumpsys wifi'), 'stuff\nmore stuff\n'),
+        # wifi_enabled
+        (self.call.adb.Shell('dumpsys wifi'), self.TimeoutError())):
+      with self.assertRaises(device_errors.CommandTimeoutError):
+        self.device.WaitUntilFullyBooted(wifi=True)
 
 
+@mock.patch('time.sleep', mock.Mock())
 class DeviceUtilsRebootTest(DeviceUtilsNewImplTest):
 
   def testReboot_nonBlocking(self):
-    self.adb.Reboot = mock.Mock()
-    self.device.IsOnline = mock.Mock(return_value=False)
-    self.device.Reboot(block=False)
-    self.adb.Reboot.assert_called_once_with()
-    self.device.IsOnline.assert_called_once_with()
+    with self.assertCalls(
+        self.call.adb.Reboot(),
+        (self.call.device.IsOnline(), True),
+        (self.call.device.IsOnline(), False)):
+      self.device.Reboot(block=False)
 
   def testReboot_blocking(self):
-    self.adb.Reboot = mock.Mock()
-    self.device.IsOnline = mock.Mock(return_value=False)
-    with self.assertShellCallSequence([
-        # sc_card_ready
-        ('echo $EXTERNAL_STORAGE', '/fake/storage/path\r\n'),
-        ('test -d /fake/storage/path', ''),
-        # pm_ready
-        ('pm path android', 'package:this.is.a.test.package\r\n'),
-        # boot_completed
-        ('getprop sys.boot_completed', '1\r\n')]):
+    with self.assertCalls(
+        self.call.adb.Reboot(),
+        (self.call.device.IsOnline(), True),
+        (self.call.device.IsOnline(), False),
+        self.call.device.WaitUntilFullyBooted()):
       self.device.Reboot(block=True)
-      self.adb.Reboot.assert_called_once_with()
-      self.device.IsOnline.assert_called_once_with()
-      self.adb.WaitForDevice.assert_called_once_with()
 
 
 class DeviceUtilsInstallTest(DeviceUtilsOldImplTest):
@@ -656,24 +571,31 @@
 
 
 class DeviceUtilsRunShellCommandTest(DeviceUtilsNewImplTest):
+
+  def setUp(self):
+    super(DeviceUtilsRunShellCommandTest, self).setUp()
+    self.device.NeedsSU = mock.Mock(return_value=False)
+
   def testRunShellCommand_commandAsList(self):
-    with self.assertShellCall('pm list packages'):
+    with self.assertCall(self.call.adb.Shell('pm list packages'), ''):
       self.device.RunShellCommand(['pm', 'list', 'packages'])
 
   def testRunShellCommand_commandAsListQuoted(self):
-    with self.assertShellCall("echo 'hello world' '$10'"):
+    with self.assertCall(self.call.adb.Shell("echo 'hello world' '$10'"), ''):
       self.device.RunShellCommand(['echo', 'hello world', '$10'])
 
   def testRunShellCommand_commandAsString(self):
-    with self.assertShellCall('echo "$VAR"'):
+    with self.assertCall(self.call.adb.Shell('echo "$VAR"'), ''):
       self.device.RunShellCommand('echo "$VAR"')
 
   def testNewRunShellImpl_withEnv(self):
-    with self.assertShellCall('VAR=some_string echo "$VAR"'):
+    with self.assertCall(
+        self.call.adb.Shell('VAR=some_string echo "$VAR"'), ''):
       self.device.RunShellCommand('echo "$VAR"', env={'VAR': 'some_string'})
 
   def testNewRunShellImpl_withEnvQuoted(self):
-    with self.assertShellCall('PATH="$PATH:/other/path" run_this'):
+    with self.assertCall(
+        self.call.adb.Shell('PATH="$PATH:/other/path" run_this'), ''):
       self.device.RunShellCommand('run_this', env={'PATH': '$PATH:/other/path'})
 
   def testNewRunShellImpl_withEnv_failure(self):
@@ -681,132 +603,129 @@
       self.device.RunShellCommand('some_cmd', env={'INVALID NAME': 'value'})
 
   def testNewRunShellImpl_withCwd(self):
-    with self.assertShellCall('cd /some/test/path && ls'):
+    with self.assertCall(self.call.adb.Shell('cd /some/test/path && ls'), ''):
       self.device.RunShellCommand('ls', cwd='/some/test/path')
 
   def testNewRunShellImpl_withCwdQuoted(self):
-    with self.assertShellCall("cd '/some test/path with/spaces' && ls"):
+    with self.assertCall(
+        self.call.adb.Shell("cd '/some test/path with/spaces' && ls"), ''):
       self.device.RunShellCommand('ls', cwd='/some test/path with/spaces')
 
   def testRunShellCommand_withSu(self):
-    with self.assertShellCallSequence([
-        ('su -c ls /root && ! ls /root', ''),
-        ('su -c setprop service.adb.root 0', '')]):
-      self.device.RunShellCommand('setprop service.adb.root 0', as_root=True)
-
-  def testRunShellCommand_withRoot(self):
-    with self.assertShellCallSequence([
-        ('su -c ls /root && ! ls /root', _ShellError()),
-        ('setprop service.adb.root 0', '')]):
+    with self.assertCalls(
+        (self.call.device.NeedsSU(), True),
+        (self.call.adb.Shell('su -c setprop service.adb.root 0'), '')):
       self.device.RunShellCommand('setprop service.adb.root 0', as_root=True)
 
   def testRunShellCommand_manyLines(self):
     cmd = 'ls /some/path'
-    with self.assertShellCall(cmd, 'file1\r\nfile2\r\nfile3\r\n'):
+    with self.assertCall(self.call.adb.Shell(cmd), 'file1\nfile2\nfile3\n'):
       self.assertEquals(['file1', 'file2', 'file3'],
                         self.device.RunShellCommand(cmd))
 
   def testRunShellCommand_singleLine_success(self):
     cmd = 'echo $VALUE'
-    with self.assertShellCall(cmd, 'some value\r\n'):
+    with self.assertCall(self.call.adb.Shell(cmd), 'some value\n'):
       self.assertEquals('some value',
                         self.device.RunShellCommand(cmd, single_line=True))
 
   def testRunShellCommand_singleLine_successEmptyLine(self):
     cmd = 'echo $VALUE'
-    with self.assertShellCall(cmd, '\r\n'):
+    with self.assertCall(self.call.adb.Shell(cmd), '\n'):
       self.assertEquals('',
                         self.device.RunShellCommand(cmd, single_line=True))
 
   def testRunShellCommand_singleLine_successWithoutEndLine(self):
     cmd = 'echo -n $VALUE'
-    with self.assertShellCall(cmd, 'some value'):
+    with self.assertCall(self.call.adb.Shell(cmd), 'some value'):
       self.assertEquals('some value',
                         self.device.RunShellCommand(cmd, single_line=True))
 
   def testRunShellCommand_singleLine_successNoOutput(self):
     cmd = 'echo -n $VALUE'
-    with self.assertShellCall(cmd, ''):
+    with self.assertCall(self.call.adb.Shell(cmd), ''):
       self.assertEquals('',
                         self.device.RunShellCommand(cmd, single_line=True))
 
   def testRunShellCommand_singleLine_failTooManyLines(self):
     cmd = 'echo $VALUE'
-    with self.assertShellCall(cmd, 'some value\r\nanother value\r\n'):
+    with self.assertCall(self.call.adb.Shell(cmd),
+                         'some value\nanother value\n'):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.RunShellCommand(cmd, single_line=True)
 
   def testRunShellCommand_checkReturn_success(self):
     cmd = 'echo $ANDROID_DATA'
-    output = '/data\r\n'
-    with self.assertShellCall(cmd, output):
+    output = '/data\n'
+    with self.assertCall(self.call.adb.Shell(cmd), output):
       self.assertEquals([output.rstrip()],
                         self.device.RunShellCommand(cmd, check_return=True))
 
   def testRunShellCommand_checkReturn_failure(self):
     cmd = 'ls /root'
-    output = 'opendir failed, Permission denied\r\n'
-    with self.assertShellCall(cmd, _ShellError(output)):
+    output = 'opendir failed, Permission denied\n'
+    with self.assertCall(self.call.adb.Shell(cmd), self.ShellError(output)):
       with self.assertRaises(device_errors.AdbShellCommandFailedError):
         self.device.RunShellCommand(cmd, check_return=True)
 
   def testRunShellCommand_checkReturn_disabled(self):
     cmd = 'ls /root'
-    output = 'opendir failed, Permission denied\r\n'
-    with self.assertShellCall(cmd, _ShellError(output)):
+    output = 'opendir failed, Permission denied\n'
+    with self.assertCall(self.call.adb.Shell(cmd), self.ShellError(output)):
       self.assertEquals([output.rstrip()],
                         self.device.RunShellCommand(cmd, check_return=False))
 
 
+@mock.patch('time.sleep', mock.Mock())
 class DeviceUtilsKillAllTest(DeviceUtilsNewImplTest):
 
   def testKillAll_noMatchingProcesses(self):
-    output = 'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n'
-    with self.assertShellCallSequence([('ps', output)]):
+    with self.assertCall(self.call.adb.Shell('ps'),
+        'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n'):
       with self.assertRaises(device_errors.CommandFailedError):
         self.device.KillAll('test_process')
 
   def testKillAll_nonblocking(self):
-    with self.assertShellCallSequence([
-        ('ps', 'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n'
-               'u0_a1  1234  174   123456 54321 ffffffff 456789ab '
-               'this.is.a.test.process\r\n'),
-        ('kill -9 1234', '')]):
+    with self.assertCalls(
+        (self.call.adb.Shell('ps'),
+         'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n'
+         'u0_a1  1234  174   123456 54321 ffffffff 456789ab some.process\n'),
+        (self.call.adb.Shell('kill -9 1234'), '')):
       self.assertEquals(1,
-          self.device.KillAll('this.is.a.test.process', blocking=False))
+          self.device.KillAll('some.process', blocking=False))
 
   def testKillAll_blocking(self):
-    with mock.patch('time.sleep'):
-      with self.assertShellCallSequence([
-        ('ps', 'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n'
-               'u0_a1  1234  174   123456 54321 ffffffff 456789ab '
-               'this.is.a.test.process\r\n'),
-        ('kill -9 1234', ''),
-        ('ps', 'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n'
-               'u0_a1  1234  174   123456 54321 ffffffff 456789ab '
-               'this.is.a.test.process\r\n'),
-        ('ps', 'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n')]):
-        self.assertEquals(1,
-            self.device.KillAll('this.is.a.test.process', blocking=True))
+    with self.assertCalls(
+        (self.call.adb.Shell('ps'),
+         'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n'
+         'u0_a1  1234  174   123456 54321 ffffffff 456789ab some.process\n'),
+        (self.call.adb.Shell('kill -9 1234'), ''),
+        (self.call.adb.Shell('ps'),
+         'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n'
+         'u0_a1  1234  174   123456 54321 ffffffff 456789ab some.process\n'),
+        (self.call.adb.Shell('ps'),
+         'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n')):
+      self.assertEquals(1,
+          self.device.KillAll('some.process', blocking=True))
 
   def testKillAll_root(self):
-    with self.assertShellCallSequence([
-        ('ps', 'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n'
-               'u0_a1  1234  174   123456 54321 ffffffff 456789ab '
-               'this.is.a.test.process\r\n'),
-        ('su -c ls /root && ! ls /root', ''),
-        ('su -c kill -9 1234', '')]):
+    with self.assertCalls(
+        (self.call.adb.Shell('ps'),
+         'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n'
+         'u0_a1  1234  174   123456 54321 ffffffff 456789ab some.process\n'),
+        (self.call.device.NeedsSU(), True),
+        (self.call.adb.Shell('su -c kill -9 1234'), '')):
       self.assertEquals(1,
-          self.device.KillAll('this.is.a.test.process', as_root=True))
+          self.device.KillAll('some.process', as_root=True))
 
   def testKillAll_sigterm(self):
-    with self.assertShellCallSequence([
-        ('ps', 'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n'
-               'u0_a1  1234  174   123456 54321 ffffffff 456789ab '
-               'this.is.a.test.process\r\n'),
-        ('kill -15 1234', '')]):
+    with self.assertCalls(
+        (self.call.adb.Shell('ps'),
+         'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n'
+         'u0_a1  1234  174   123456 54321 ffffffff 456789ab some.process\n'),
+        (self.call.adb.Shell('kill -15 1234'), '')):
       self.assertEquals(1,
-          self.device.KillAll('this.is.a.test.process', signum=signal.SIGTERM))
+          self.device.KillAll('some.process', signum=signal.SIGTERM))
 
 
 class DeviceUtilsStartActivityTest(DeviceUtilsOldImplTest):
@@ -974,6 +893,48 @@
       self.device.StartActivity(test_intent)
 
 
+class DeviceUtilsStartInstrumentationTest(DeviceUtilsNewImplTest):
+
+  def testStartInstrumentation_nothing(self):
+    with self.assertCalls(
+        self.call.device.RunShellCommand(
+            ['am', 'instrument', 'test.package/.TestInstrumentation'],
+            check_return=True)):
+      self.device.StartInstrumentation(
+          'test.package/.TestInstrumentation',
+          finish=False, raw=False, extras=None)
+
+  def testStartInstrumentation_finish(self):
+    with self.assertCalls(
+        (self.call.device.RunShellCommand(
+            ['am', 'instrument', '-w', 'test.package/.TestInstrumentation'],
+            check_return=True),
+         ['OK (1 test)'])):
+      output = self.device.StartInstrumentation(
+          'test.package/.TestInstrumentation',
+          finish=True, raw=False, extras=None)
+      self.assertEquals(['OK (1 test)'], output)
+
+  def testStartInstrumentation_raw(self):
+    with self.assertCalls(
+        self.call.device.RunShellCommand(
+            ['am', 'instrument', '-r', 'test.package/.TestInstrumentation'],
+            check_return=True)):
+      self.device.StartInstrumentation(
+          'test.package/.TestInstrumentation',
+          finish=False, raw=True, extras=None)
+
+  def testStartInstrumentation_extras(self):
+    with self.assertCalls(
+        self.call.device.RunShellCommand(
+            ['am', 'instrument', '-e', 'foo', 'Foo', '-e', 'bar', 'Bar',
+             'test.package/.TestInstrumentation'],
+            check_return=True)):
+      self.device.StartInstrumentation(
+          'test.package/.TestInstrumentation',
+          finish=False, raw=False, extras={'foo': 'Foo', 'bar': 'Bar'})
+
+
 class DeviceUtilsBroadcastIntentTest(DeviceUtilsOldImplTest):
 
   def testBroadcastIntent_noExtras(self):
@@ -1056,93 +1017,61 @@
 
   def testPushChangedFilesIndividually_empty(self):
     test_files = []
-    self.device._PushChangedFilesIndividually(test_files)
-    self.assertEqual(0, self.adb.Push.call_count)
+    with self.assertCalls():
+      self.device._PushChangedFilesIndividually(test_files)
 
   def testPushChangedFilesIndividually_single(self):
     test_files = [('/test/host/path', '/test/device/path')]
-    self.device._PushChangedFilesIndividually(test_files)
-    self.adb.Push.assert_called_once_with(
-        '/test/host/path', '/test/device/path')
+    with self.assertCalls(self.call.adb.Push(*test_files[0])):
+      self.device._PushChangedFilesIndividually(test_files)
 
   def testPushChangedFilesIndividually_multiple(self):
     test_files = [
         ('/test/host/path/file1', '/test/device/path/file1'),
         ('/test/host/path/file2', '/test/device/path/file2')]
-    self.device._PushChangedFilesIndividually(test_files)
-    self.assertEqual(2, self.adb.Push.call_count)
-    self.adb.Push.assert_any_call(
-        '/test/host/path/file1', '/test/device/path/file1')
-    self.adb.Push.assert_any_call(
-        '/test/host/path/file2', '/test/device/path/file2')
+    with self.assertCalls(
+        self.call.adb.Push(*test_files[0]),
+        self.call.adb.Push(*test_files[1])):
+      self.device._PushChangedFilesIndividually(test_files)
 
 
-@mock.patch('pylib.device.commands.install_commands.Installed', new=None)
-@mock.patch('pylib.device.commands.install_commands.InstallCommands', new=None)
-class DeviceUtilsPushChangedFilesZippedTest(DeviceUtilsHybridImplTest):
-
-  def setUp(self):
-    super(DeviceUtilsPushChangedFilesZippedTest, self).setUp()
+class DeviceUtilsPushChangedFilesZippedTest(DeviceUtilsNewImplTest):
 
   def testPushChangedFilesZipped_empty(self):
     test_files = []
-    self.device._PushChangedFilesZipped(test_files)
-    self.assertEqual(0, self.adb.Push.call_count)
+    with self.assertCalls():
+      self.device._PushChangedFilesZipped(test_files)
+
+  def _testPushChangedFilesZipped_spec(self, test_files):
+    mock_zip_temp = mock.mock_open()
+    mock_zip_temp.return_value.name = '/test/temp/file/tmp.zip'
+    with self.assertCalls(
+        (mock.call.tempfile.NamedTemporaryFile(suffix='.zip'), mock_zip_temp),
+        (mock.call.multiprocessing.Process(
+            target=device_utils.DeviceUtils._CreateDeviceZip,
+            args=('/test/temp/file/tmp.zip', test_files)), mock.Mock()),
+        (self.call.device.GetExternalStoragePath(),
+         '/test/device/external_dir'),
+        self.call.adb.Push(
+            '/test/temp/file/tmp.zip', '/test/device/external_dir/tmp.zip'),
+        self.call.device.RunShellCommand(
+            ['unzip', '/test/device/external_dir/tmp.zip'],
+            as_root=True,
+            env={'PATH': '$PATH:/data/local/tmp/bin'},
+            check_return=True),
+        (self.call.device.IsOnline(), True),
+        self.call.device.RunShellCommand(
+            ['rm', '/test/device/external_dir/tmp.zip'], check_return=True)):
+      self.device._PushChangedFilesZipped(test_files)
 
   def testPushChangedFilesZipped_single(self):
-    test_files = [('/test/host/path/file1', '/test/device/path/file1')]
-
-    self.device._GetExternalStoragePathImpl = mock.Mock(
-        return_value='/test/device/external_dir')
-    self.device.IsOnline = mock.Mock(return_value=True)
-    self.device.RunShellCommand = mock.Mock()
-    mock_zip_temp = mock.mock_open()
-    mock_zip_temp.return_value.name = '/test/temp/file/tmp.zip'
-    with mock.patch('multiprocessing.Process') as mock_zip_proc, (
-         mock.patch('tempfile.NamedTemporaryFile', mock_zip_temp)):
-      self.device._PushChangedFilesZipped(test_files)
-
-    mock_zip_proc.assert_called_once_with(
-        target=device_utils.DeviceUtils._CreateDeviceZip,
-        args=('/test/temp/file/tmp.zip', test_files))
-    self.adb.Push.assert_called_once_with(
-        '/test/temp/file/tmp.zip', '/test/device/external_dir/tmp.zip')
-    self.assertEqual(2, self.device.RunShellCommand.call_count)
-    self.device.RunShellCommand.assert_any_call(
-        ['unzip', '/test/device/external_dir/tmp.zip'],
-        as_root=True,
-        env={'PATH': '$PATH:/data/local/tmp/bin'},
-        check_return=True)
-    self.device.RunShellCommand.assert_any_call(
-        ['rm', '/test/device/external_dir/tmp.zip'], check_return=True)
+    self._testPushChangedFilesZipped_spec(
+        [('/test/host/path/file1', '/test/device/path/file1')])
 
   def testPushChangedFilesZipped_multiple(self):
-    test_files = [('/test/host/path/file1', '/test/device/path/file1'),
-                  ('/test/host/path/file2', '/test/device/path/file2')]
-
-    self.device._GetExternalStoragePathImpl = mock.Mock(
-        return_value='/test/device/external_dir')
-    self.device.IsOnline = mock.Mock(return_value=True)
-    self.device.RunShellCommand = mock.Mock()
-    mock_zip_temp = mock.mock_open()
-    mock_zip_temp.return_value.name = '/test/temp/file/tmp.zip'
-    with mock.patch('multiprocessing.Process') as mock_zip_proc, (
-         mock.patch('tempfile.NamedTemporaryFile', mock_zip_temp)):
-      self.device._PushChangedFilesZipped(test_files)
-
-    mock_zip_proc.assert_called_once_with(
-        target=device_utils.DeviceUtils._CreateDeviceZip,
-        args=('/test/temp/file/tmp.zip', test_files))
-    self.adb.Push.assert_called_once_with(
-        '/test/temp/file/tmp.zip', '/test/device/external_dir/tmp.zip')
-    self.assertEqual(2, self.device.RunShellCommand.call_count)
-    self.device.RunShellCommand.assert_any_call(
-        ['unzip', '/test/device/external_dir/tmp.zip'],
-        as_root=True,
-        env={'PATH': '$PATH:/data/local/tmp/bin'},
-        check_return=True)
-    self.device.RunShellCommand.assert_any_call(
-        ['rm', '/test/device/external_dir/tmp.zip'], check_return=True)
+    self._testPushChangedFilesZipped_spec(
+        [('/test/host/path/file1', '/test/device/path/file1'),
+         ('/test/host/path/file2', '/test/device/path/file2')])
 
 
 class DeviceUtilsFileExistsTest(DeviceUtilsOldImplTest):
@@ -1370,23 +1299,27 @@
       self.device.WriteFile('/test/file/no.permissions.to.write',
                             'new test file contents', as_root=True)
 
+
 class DeviceUtilsWriteTextFileTest(DeviceUtilsNewImplTest):
 
   def testWriteTextFileTest_basic(self):
-    with self.assertShellCall('echo some.string > /test/file/to.write'):
+    with self.assertCall(
+        self.call.adb.Shell('echo some.string > /test/file/to.write'), ''):
       self.device.WriteTextFile('/test/file/to.write', 'some.string')
 
   def testWriteTextFileTest_quoted(self):
-    with self.assertShellCall(
-        "echo 'some other string' > '/test/file/to write'"):
+    with self.assertCall(
+        self.call.adb.Shell("echo 'some other string' > '/test/file/to write'"),
+        ''):
       self.device.WriteTextFile('/test/file/to write', 'some other string')
 
-  def testWriteTextFileTest_asRoot(self):
-    with self.assertShellCallSequence([
-        ('su -c ls /root && ! ls /root', ''),
-        ('su -c echo string > /test/file', '')]):
+  def testWriteTextFileTest_withSU(self):
+    with self.assertCalls(
+        (self.call.device.NeedsSU(), True),
+        (self.call.adb.Shell('su -c echo string > /test/file'), '')):
       self.device.WriteTextFile('/test/file', 'string', as_root=True)
 
+
 class DeviceUtilsLsTest(DeviceUtilsOldImplTest):
 
   def testLs_nothing(self):
@@ -1512,29 +1445,29 @@
 class DeviceUtilsGetPropTest(DeviceUtilsNewImplTest):
 
   def testGetProp_exists(self):
-    with self.assertShellCall('getprop this.is.a.test.property',
-                              'test_property_value\r\n'):
-      self.assertEqual('test_property_value',
-                       self.device.GetProp('this.is.a.test.property'))
+    with self.assertCall(
+        self.call.adb.Shell('getprop test.property'), 'property_value\n'):
+      self.assertEqual('property_value',
+                       self.device.GetProp('test.property'))
 
   def testGetProp_doesNotExist(self):
-    with self.assertShellCall('getprop this.property.does.not.exist',
-                              '\r\n'):
-      self.assertEqual('', self.device.GetProp('this.property.does.not.exist'))
+    with self.assertCall(
+        self.call.adb.Shell('getprop property.does.not.exist'), '\n'):
+      self.assertEqual('', self.device.GetProp('property.does.not.exist'))
 
   def testGetProp_cachedRoProp(self):
-    with self.assertShellCall('getprop ro.build.type',
-                              'userdebug\r\n'):
+    with self.assertCall(
+        self.call.adb.Shell('getprop ro.build.type'), 'userdebug\n'):
       self.assertEqual('userdebug',
                        self.device.GetProp('ro.build.type', cache=True))
       self.assertEqual('userdebug',
                        self.device.GetProp('ro.build.type', cache=True))
 
   def testGetProp_retryAndCache(self):
-    with self.assertShellCallSequence([
-        ('getprop ro.build.type', _ShellError()),
-        ('getprop ro.build.type', _ShellError()),
-        ('getprop ro.build.type', 'userdebug\r\n')]):
+    with self.assertCalls(
+        (self.call.adb.Shell('getprop ro.build.type'), self.ShellError()),
+        (self.call.adb.Shell('getprop ro.build.type'), self.ShellError()),
+        (self.call.adb.Shell('getprop ro.build.type'), 'userdebug\n')):
       self.assertEqual('userdebug',
                        self.device.GetProp('ro.build.type',
                                            cache=True, retries=3))
@@ -1546,46 +1479,55 @@
 class DeviceUtilsSetPropTest(DeviceUtilsNewImplTest):
 
   def testSetProp(self):
-    with self.assertShellCall(
-      "setprop this.is.a.test.property 'test property value'"):
-      self.device.SetProp('this.is.a.test.property', 'test property value')
+    with self.assertCall(
+        self.call.adb.Shell("setprop test.property 'test value'"), ''):
+      self.device.SetProp('test.property', 'test value')
+
+  def testSetProp_check_succeeds(self):
+    with self.assertCalls(
+        (self.call.adb.Shell('setprop test.property new_value'), ''),
+        (self.call.adb.Shell('getprop test.property'), 'new_value')):
+      self.device.SetProp('test.property', 'new_value', check=True)
+
+  def testSetProp_check_fails(self):
+    with self.assertCalls(
+        (self.call.adb.Shell('setprop test.property new_value'), ''),
+        (self.call.adb.Shell('getprop test.property'), 'old_value')):
+      with self.assertRaises(device_errors.CommandFailedError):
+        self.device.SetProp('test.property', 'new_value', check=True)
 
 
 class DeviceUtilsGetPidsTest(DeviceUtilsNewImplTest):
 
   def testGetPids_noMatches(self):
-    with self.assertShellCall(
-        'ps',
-        'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n'
-        'user  1000    100   1024 1024   ffffffff 00000000 no.match\r\n'):
+    with self.assertCall(self.call.adb.Shell('ps'),
+        'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n'
+        'user  1000    100   1024 1024   ffffffff 00000000 no.match\n'):
       self.assertEqual({}, self.device.GetPids('does.not.match'))
 
   def testGetPids_oneMatch(self):
-    with self.assertShellCall(
-        'ps',
-        'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n'
-        'user  1000    100   1024 1024   ffffffff 00000000 not.a.match\r\n'
-        'user  1001    100   1024 1024   ffffffff 00000000 one.match\r\n'):
+    with self.assertCall(self.call.adb.Shell('ps'),
+        'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n'
+        'user  1000    100   1024 1024   ffffffff 00000000 not.a.match\n'
+        'user  1001    100   1024 1024   ffffffff 00000000 one.match\n'):
       self.assertEqual({'one.match': '1001'}, self.device.GetPids('one.match'))
 
   def testGetPids_mutlipleMatches(self):
-    with self.assertShellCall(
-        'ps',
-        'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n'
-        'user  1000    100   1024 1024   ffffffff 00000000 not\r\n'
-        'user  1001    100   1024 1024   ffffffff 00000000 one.match\r\n'
-        'user  1002    100   1024 1024   ffffffff 00000000 two.match\r\n'
-        'user  1003    100   1024 1024   ffffffff 00000000 three.match\r\n'):
+    with self.assertCall(self.call.adb.Shell('ps'),
+        'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n'
+        'user  1000    100   1024 1024   ffffffff 00000000 not\n'
+        'user  1001    100   1024 1024   ffffffff 00000000 one.match\n'
+        'user  1002    100   1024 1024   ffffffff 00000000 two.match\n'
+        'user  1003    100   1024 1024   ffffffff 00000000 three.match\n'):
       self.assertEqual(
           {'one.match': '1001', 'two.match': '1002', 'three.match': '1003'},
           self.device.GetPids('match'))
 
   def testGetPids_exactMatch(self):
-    with self.assertShellCall(
-        'ps',
-        'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\r\n'
-        'user  1000    100   1024 1024   ffffffff 00000000 not.exact.match\r\n'
-        'user  1234    100   1024 1024   ffffffff 00000000 exact.match\r\n'):
+    with self.assertCall(self.call.adb.Shell('ps'),
+        'USER   PID   PPID  VSIZE  RSS   WCHAN    PC       NAME\n'
+        'user  1000    100   1024 1024   ffffffff 00000000 not.exact.match\n'
+        'user  1234    100   1024 1024   ffffffff 00000000 exact.match\n'):
       self.assertEqual(
           {'not.exact.match': '1000', 'exact.match': '1234'},
           self.device.GetPids('exact.match'))
@@ -1668,6 +1610,7 @@
 
 
 class DeviceUtilsStrTest(DeviceUtilsOldImplTest):
+
   def testStr_noAdbCalls(self):
     with self.assertNoAdbCalls():
       self.assertEqual('0123456789abcdef', str(self.device))
diff --git a/build/android/pylib/forwarder.py b/build/android/pylib/forwarder.py
index 5e45043..5f40a94 100644
--- a/build/android/pylib/forwarder.py
+++ b/build/android/pylib/forwarder.py
@@ -270,6 +270,7 @@
       pid_file.seek(0)
       pid_file.write(
           '%s:%s' % (pid_for_lock, str(_GetProcessStartTime(pid_for_lock))))
+      pid_file.truncate()
 
   def _InitDeviceLocked(self, device, tool):
     """Initializes the device_forwarder daemon for a specific device (once).
diff --git a/build/android/pylib/gtest/test_runner.py b/build/android/pylib/gtest/test_runner.py
index 75892ee..56e7449 100644
--- a/build/android/pylib/gtest/test_runner.py
+++ b/build/android/pylib/gtest/test_runner.py
@@ -7,9 +7,11 @@
 import re
 
 from pylib import pexpect
+from pylib import ports
 from pylib.base import base_test_result
 from pylib.base import base_test_runner
 from pylib.device import device_errors
+from pylib.local import local_test_server_spawner
 from pylib.perf import perf_control
 
 
@@ -53,6 +55,13 @@
     if _TestSuiteRequiresHighPerfMode(self.test_package.suite_name):
       self._perf_controller = perf_control.PerfControl(self.device)
 
+    if _TestSuiteRequiresMockTestServer(self.test_package.suite_name):
+      self._servers = [
+          local_test_server_spawner.LocalTestServerSpawner(
+              ports.AllocateTestServerPort(), self.device, self.tool)]
+    else:
+      self._servers = []
+
   #override
   def InstallTestPackage(self):
     self.test_package.Install(self.device)
@@ -149,7 +158,8 @@
       test_results = self._ParseTestOutput(
           self.test_package.SpawnTestProcess(self.device))
     finally:
-      self.CleanupSpawningServerState()
+      for s in self._servers:
+        s.Reset()
     # Calculate unknown test results.
     all_tests = set(test.split(':'))
     all_tests_ran = set([t.GetName() for t in test_results.GetAll()])
@@ -164,8 +174,8 @@
   def SetUp(self):
     """Sets up necessary test enviroment for the test suite."""
     super(TestRunner, self).SetUp()
-    if _TestSuiteRequiresMockTestServer(self.test_package.suite_name):
-      self.LaunchChromeTestServerSpawner()
+    for s in self._servers:
+      s.SetUp()
     if _TestSuiteRequiresHighPerfMode(self.test_package.suite_name):
       self._perf_controller.SetHighPerfMode()
     self.tool.SetupEnvironment()
@@ -173,6 +183,8 @@
   #override
   def TearDown(self):
     """Cleans up the test enviroment for the test suite."""
+    for s in self._servers:
+      s.TearDown()
     if _TestSuiteRequiresHighPerfMode(self.test_package.suite_name):
       self._perf_controller.SetDefaultPerfMode()
     self.test_package.ClearApplicationState(self.device)
diff --git a/build/android/pylib/instrumentation/test_jar.py b/build/android/pylib/instrumentation/test_jar.py
index c81258f..d83d00a 100644
--- a/build/android/pylib/instrumentation/test_jar.py
+++ b/build/android/pylib/instrumentation/test_jar.py
@@ -23,7 +23,7 @@
 import unittest_util # pylint: disable=F0401
 
 # If you change the cached output of proguard, increment this number
-PICKLE_FORMAT_VERSION = 2
+PICKLE_FORMAT_VERSION = 3
 
 
 class TestJar(object):
@@ -55,6 +55,16 @@
     if not self._GetCachedProguardData():
       self._GetProguardData()
 
+  @staticmethod
+  def _CalculateMd5(path):
+    # TODO(jbudorick): Move MD5sum calculations out of here and
+    # AndroidCommands to their own module.
+    out = cmd_helper.GetCmdOutput(
+        [os.path.join(constants.GetOutDirectory(),
+                      'md5sum_bin_host'),
+        path])
+    return out
+
   def _GetCachedProguardData(self):
     if (os.path.exists(self._pickled_proguard_name) and
         (os.path.getmtime(self._pickled_proguard_name) >
@@ -64,7 +74,9 @@
       try:
         with open(self._pickled_proguard_name, 'r') as r:
           d = pickle.loads(r.read())
-        if d['VERSION'] == PICKLE_FORMAT_VERSION:
+        jar_md5 = self._CalculateMd5(self._jar_path)
+        if (d['JAR_MD5SUM'] == jar_md5 and
+            d['VERSION'] == PICKLE_FORMAT_VERSION):
           self._test_methods = d['TEST_METHODS']
           return True
       except:
@@ -182,7 +194,8 @@
 
     logging.info('Storing proguard output to %s', self._pickled_proguard_name)
     d = {'VERSION': PICKLE_FORMAT_VERSION,
-         'TEST_METHODS': self._test_methods}
+         'TEST_METHODS': self._test_methods,
+         'JAR_MD5SUM': self._CalculateMd5(self._jar_path)}
     with open(self._pickled_proguard_name, 'w') as f:
       f.write(pickle.dumps(d))
 
diff --git a/build/android/pylib/instrumentation/test_result.py b/build/android/pylib/instrumentation/test_result.py
index a0eea65..24e80a8 100644
--- a/build/android/pylib/instrumentation/test_result.py
+++ b/build/android/pylib/instrumentation/test_result.py
@@ -18,7 +18,8 @@
       dur: Duration of the test run in milliseconds.
       log: A string listing any errors.
     """
-    super(InstrumentationTestResult, self).__init__(full_name, test_type, log)
+    super(InstrumentationTestResult, self).__init__(
+        full_name, test_type, dur, log)
     name_pieces = full_name.rsplit('#')
     if len(name_pieces) > 1:
       self._test_name = name_pieces[1]
@@ -27,8 +28,3 @@
       self._class_name = full_name
       self._test_name = full_name
     self._start_date = start_date
-    self._dur = dur
-
-  def GetDur(self):
-    """Get the test duration."""
-    return self._dur
diff --git a/build/android/pylib/instrumentation/test_runner.py b/build/android/pylib/instrumentation/test_runner.py
index 3bac503..60e00f3 100644
--- a/build/android/pylib/instrumentation/test_runner.py
+++ b/build/android/pylib/instrumentation/test_runner.py
@@ -369,16 +369,11 @@
     Returns:
       The raw output of am instrument as a list of lines.
     """
-    # Build the 'am instrument' command
-    instrumentation_path = (
-        '%s/%s' % (self.test_pkg.GetPackageName(), self.options.test_runner))
-
-    cmd = ['am', 'instrument', '-r']
-    for k, v in self._GetInstrumentationArgs().iteritems():
-      cmd.extend(['-e', k, v])
-    cmd.extend(['-e', 'class', test])
-    cmd.extend(['-w', instrumentation_path])
-    return self.device.RunShellCommand(cmd, timeout=timeout, retries=0)
+    extras = self._GetInstrumentationArgs()
+    extras['class'] = test
+    return self.device.StartInstrumentation(
+        '%s/%s' % (self.test_pkg.GetPackageName(), self.options.test_runner),
+        raw=True, extras=extras, timeout=timeout, retries=0)
 
   @staticmethod
   def _ParseAmInstrumentRawOutput(raw_output):
@@ -506,7 +501,7 @@
                self.tool.GetTimeoutScale())
     if (self.device.GetProp('ro.build.version.sdk')
         < constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN):
-      timeout *= 4
+      timeout *= 10
 
     start_ms = 0
     duration_ms = 0
diff --git a/build/android/pylib/instrumentation/test_runner_test.py b/build/android/pylib/instrumentation/test_runner_test.py
index 039bb03..0f2845e 100755
--- a/build/android/pylib/instrumentation/test_runner_test.py
+++ b/build/android/pylib/instrumentation/test_runner_test.py
@@ -144,7 +144,7 @@
     self.assertEqual('test.package.TestClass#testMethod', result.GetName())
     self.assertEqual(base_test_result.ResultType.UNKNOWN, result.GetType())
     self.assertEqual('', result.GetLog())
-    self.assertEqual(1000, result.GetDur())
+    self.assertEqual(1000, result.GetDuration())
 
   def testGenerateTestResult_testPassed(self):
     statuses = [
@@ -253,17 +253,18 @@
 
   def test_RunTest_verifyAdbShellCommand(self):
     self.instance.options.test_runner = 'MyTestRunner'
-    self.instance.device.RunShellCommand = mock.Mock()
+    self.instance.device.StartInstrumentation = mock.Mock()
     self.instance.test_pkg.GetPackageName = mock.Mock(
         return_value='test.package')
     self.instance._GetInstrumentationArgs = mock.Mock(
         return_value={'test_arg_key': 'test_arg_value'})
     self.instance._RunTest('test.package.TestClass#testMethod', 100)
-    self.instance.device.RunShellCommand.assert_called_with(
-        ['am', 'instrument', '-r',
-         '-e', 'test_arg_key', 'test_arg_value',
-         '-e', 'class', 'test.package.TestClass#testMethod',
-         '-w', 'test.package/MyTestRunner'],
+    self.instance.device.StartInstrumentation.assert_called_with(
+        'test.package/MyTestRunner', raw=True,
+        extras={
+            'test_arg_key': 'test_arg_value',
+            'class': 'test.package.TestClass#testMethod'
+        },
         timeout=100, retries=0)
 
 if __name__ == '__main__':
diff --git a/build/android/pylib/linker/test_case.py b/build/android/pylib/linker/test_case.py
index 446bc84..f64a58b 100644
--- a/build/android/pylib/linker/test_case.py
+++ b/build/android/pylib/linker/test_case.py
@@ -340,7 +340,7 @@
         base_test_result.BaseTestResult(
             self.tagged_name,
             status,
-            logs))
+            log=logs))
 
     return results
 
diff --git a/build/android/pylib/local/__init__.py b/build/android/pylib/local/__init__.py
new file mode 100644
index 0000000..4d6aabb
--- /dev/null
+++ b/build/android/pylib/local/__init__.py
@@ -0,0 +1,3 @@
+# 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.
diff --git a/build/android/pylib/local/local_test_server_spawner.py b/build/android/pylib/local/local_test_server_spawner.py
new file mode 100644
index 0000000..77f552e
--- /dev/null
+++ b/build/android/pylib/local/local_test_server_spawner.py
@@ -0,0 +1,45 @@
+# 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.
+
+from pylib import chrome_test_server_spawner
+from pylib import forwarder
+from pylib.base import test_server
+
+
+class LocalTestServerSpawner(test_server.TestServer):
+
+  def __init__(self, port, device, tool):
+    super(LocalTestServerSpawner, self).__init__()
+    self._device = device
+    self._spawning_server = chrome_test_server_spawner.SpawningServer(
+        port, device, tool)
+    self._tool = tool
+
+  @property
+  def server_address(self):
+    return self._spawning_server.server.server_address
+
+  @property
+  def port(self):
+    return self.server_address[1]
+
+  #override
+  def SetUp(self):
+    self._device.WriteFile(
+        '%s/net-test-server-ports' % self._device.GetExternalStoragePath(),
+        '%s:0' % str(self.port))
+    forwarder.Forwarder.Map(
+        [(self.port, self.port)], self._device, self._tool)
+    self._spawning_server.Start()
+
+  #override
+  def Reset(self):
+    self._spawning_server.CleanupState()
+
+  #override
+  def TearDown(self):
+    self.Reset()
+    self._spawning_server.Stop()
+    forwarder.Forwarder.UnmapDevicePort(self.port, self._device)
+
diff --git a/build/android/pylib/uiautomator/test_package.py b/build/android/pylib/uiautomator/test_package.py
index fd9120e..cb51fdf 100644
--- a/build/android/pylib/uiautomator/test_package.py
+++ b/build/android/pylib/uiautomator/test_package.py
@@ -11,6 +11,11 @@
 
 
 class TestPackage(test_jar.TestJar):
+
+  UIAUTOMATOR_PATH = 'uiautomator/'
+  UIAUTOMATOR_DEVICE_DIR = os.path.join(constants.TEST_EXECUTABLE_DIR,
+                                        UIAUTOMATOR_PATH)
+
   def __init__(self, jar_path, jar_info_path):
     test_jar.TestJar.__init__(self, jar_info_path)
 
@@ -24,4 +29,5 @@
 
   # Override.
   def Install(self, device):
-    device.PushChangedFiles([(self._jar_path, constants.TEST_EXECUTABLE_DIR)])
+    device.PushChangedFiles([(self._jar_path, self.UIAUTOMATOR_DEVICE_DIR +
+                              self.GetPackageName())])
diff --git a/build/android/pylib/uiautomator/test_runner.py b/build/android/pylib/uiautomator/test_runner.py
index c0f8f15..f4e5730 100644
--- a/build/android/pylib/uiautomator/test_runner.py
+++ b/build/android/pylib/uiautomator/test_runner.py
@@ -73,7 +73,8 @@
                       package=self._package),
         blocking=True,
         force_stop=True)
-    cmd = ['uiautomator', 'runtest', self.test_pkg.GetPackageName(),
+    cmd = ['uiautomator', 'runtest',
+           self.test_pkg.UIAUTOMATOR_PATH + self.test_pkg.GetPackageName(),
            '-e', 'class', test]
     return self.device.RunShellCommand(cmd, timeout=timeout, retries=0)
 
diff --git a/build/android/pylib/utils/flakiness_dashboard_results_uploader.py b/build/android/pylib/utils/flakiness_dashboard_results_uploader.py
index 246c83b..ff286b6 100644
--- a/build/android/pylib/utils/flakiness_dashboard_results_uploader.py
+++ b/build/android/pylib/utils/flakiness_dashboard_results_uploader.py
@@ -131,7 +131,7 @@
         test_result = json_results_generator.TestResult(
             test=single_test_result.GetName(),
             failed=failed,
-            elapsed_time=single_test_result.GetDur() / 1000)
+            elapsed_time=single_test_result.GetDuration() / 1000)
         # The WebKit TestResult object sets the modifier it based on test name.
         # Since we don't use the same test naming convention as WebKit the
         # modifier will be wrong, so we need to overwrite it.
diff --git a/build/android/pylib/utils/isolator.py b/build/android/pylib/utils/isolator.py
index 9ee5671..afbee2a 100644
--- a/build/android/pylib/utils/isolator.py
+++ b/build/android/pylib/utils/isolator.py
@@ -32,7 +32,6 @@
     'component': 'static_library',
     'fastbuild': '0',
     'icu_use_data_file_flag': '1',
-    'libpeer_target_type': 'static_library',
     'lsan': '0',
     # TODO(maruel): This may not always be true.
     'target_arch': 'arm',
diff --git a/build/android/pylib/utils/mock_calls.py b/build/android/pylib/utils/mock_calls.py
new file mode 100755
index 0000000..1f9d8e3
--- /dev/null
+++ b/build/android/pylib/utils/mock_calls.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python
+# 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.
+
+"""
+A test facility to assert call sequences while mocking their behavior.
+"""
+
+import os
+import sys
+import unittest
+
+from pylib import constants
+
+sys.path.append(os.path.join(
+    constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock # pylint: disable=F0401
+
+
+class TestCase(unittest.TestCase):
+  """Adds assertCalls to TestCase objects."""
+  class _AssertCalls(object):
+    def __init__(self, test_case, expected_calls, watched):
+      def call_action(pair):
+        if isinstance(pair, type(mock.call)):
+          return (pair, None)
+        else:
+          return pair
+
+      def do_check(call):
+        def side_effect(*args, **kwargs):
+          received_call = call(*args, **kwargs)
+          self._test_case.assertTrue(
+              self._expected_calls,
+              msg=('Unexpected call: %s' % str(received_call)))
+          expected_call, action = self._expected_calls.pop(0)
+          self._test_case.assertTrue(
+              received_call == expected_call,
+              msg=('Expected call missmatch:\n'
+                   '  expected: %s\n'
+                   '  received: %s\n'
+                   % (str(expected_call), str(received_call))))
+          if callable(action):
+            return action(*args, **kwargs)
+          else:
+            return action
+        return side_effect
+
+      self._test_case = test_case
+      self._expected_calls = [call_action(pair) for pair in expected_calls]
+      watched = watched.copy() # do not pollute the caller's dict
+      watched.update((call.parent.name, call.parent)
+                     for call, _ in self._expected_calls)
+      self._patched = [test_case.patch_call(call, side_effect=do_check(call))
+                       for call in watched.itervalues()]
+
+    def __enter__(self):
+      for patch in self._patched:
+        patch.__enter__()
+      return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+      for patch in self._patched:
+        patch.__exit__(exc_type, exc_val, exc_tb)
+      if exc_type is None:
+        missing = ''.join('  expected: %s\n' % str(call)
+                          for call, _ in self._expected_calls)
+        self._test_case.assertFalse(
+            missing,
+            msg='Expected calls not found:\n' + missing)
+
+  def __init__(self, *args, **kwargs):
+    super(TestCase, self).__init__(*args, **kwargs)
+    self.call = mock.call.self
+    self._watched = {}
+
+  def call_target(self, call):
+    """Resolve a self.call instance to the target it represents.
+
+    Args:
+      call: a self.call instance, e.g. self.call.adb.Shell
+
+    Returns:
+      The target object represented by the call, e.g. self.adb.Shell
+
+    Raises:
+      ValueError if the path of the call does not start with "self", i.e. the
+          target of the call is external to the self object.
+      AttributeError if the path of the call does not specify a valid
+          chain of attributes (without any calls) starting from "self".
+    """
+    path = call.name.split('.')
+    if path.pop(0) != 'self':
+      raise ValueError("Target %r outside of 'self' object" % call.name)
+    target = self
+    for attr in path:
+      target = getattr(target, attr)
+    return target
+
+  def patch_call(self, call, **kwargs):
+    """Patch the target of a mock.call instance.
+
+    Args:
+      call: a mock.call instance identifying a target to patch
+      Extra keyword arguments are processed by mock.patch
+
+    Returns:
+      A context manager to mock/unmock the target of the call
+    """
+    if call.name.startswith('self.'):
+      target = self.call_target(call.parent)
+      _, attribute = call.name.rsplit('.', 1)
+      return mock.patch.object(target, attribute, **kwargs)
+    else:
+      return mock.patch(call.name, **kwargs)
+
+  def watchCalls(self, calls):
+    """Add calls to the set of watched calls.
+
+    Args:
+      calls: a sequence of mock.call instances identifying targets to watch
+    """
+    self._watched.update((call.name, call) for call in calls)
+
+  def watchMethodCalls(self, call):
+    """Watch all public methods of the target identified by a self.call.
+
+    Args:
+      call: a self.call instance indetifying an object
+    """
+    target = self.call_target(call)
+    self.watchCalls(getattr(call, method)
+                    for method in dir(target.__class__)
+                    if not method.startswith('_'))
+
+  def clearWatched(self):
+    """Clear the set of watched calls."""
+    self._watched = {}
+
+  def assertCalls(self, *calls):
+    """A context manager to assert that a sequence of calls is made.
+
+    During the assertion, a number of functions and methods will be "watched",
+    and any calls made to them is expected to appear---in the exact same order,
+    and with the exact same arguments---as specified by the argument |calls|.
+
+    By default, the targets of all expected calls are watched. Further targets
+    to watch may be added using watchCalls and watchMethodCalls.
+
+    Optionaly, each call may be accompanied by an action. If the action is a
+    (non-callable) value, this value will be used as the return value given to
+    the caller when the matching call is found. Alternatively, if the action is
+    a callable, the action will be then called with the same arguments as the
+    intercepted call, so that it can provide a return value or perform other
+    side effects. If the action is missing, a return value of None is assumed.
+
+    Note that mock.Mock objects are often convenient to use as a callable
+    action, e.g. to raise exceptions or return other objects which are
+    themselves callable.
+
+    Args:
+      calls: each argument is either a pair (expected_call, action) or just an
+          expected_call, where expected_call is a mock.call instance.
+
+    Raises:
+      AssertionError if the watched targets do not receive the exact sequence
+          of calls specified. Missing calls, extra calls, and calls with
+          mismatching arguments, all cause the assertion to fail.
+    """
+    return self._AssertCalls(self, calls, self._watched)
+
+  def assertCall(self, call, action=None):
+    return self.assertCalls((call, action))
+
diff --git a/build/android/pylib/utils/mock_calls_test.py b/build/android/pylib/utils/mock_calls_test.py
new file mode 100755
index 0000000..1b474af
--- /dev/null
+++ b/build/android/pylib/utils/mock_calls_test.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+# 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.
+
+"""
+Unit tests for the contents of mock_calls.py.
+"""
+
+import logging
+import os
+import sys
+import unittest
+
+from pylib import constants
+from pylib.utils import mock_calls
+
+sys.path.append(os.path.join(
+    constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock # pylint: disable=F0401
+
+
+class _DummyAdb(object):
+  def __str__(self):
+    return '0123456789abcdef'
+
+  def Push(self, host_path, device_path):
+    logging.debug('(device %s) pushing %r to %r', self, host_path, device_path)
+
+  def IsOnline(self):
+    logging.debug('(device %s) checking device online', self)
+    return True
+
+  def Shell(self, cmd):
+    logging.debug('(device %s) running command %r', self, cmd)
+    return "nice output\n"
+
+  def Reboot(self):
+    logging.debug('(device %s) rebooted!', self)
+
+
+class TestCaseWithAssertCallsTest(mock_calls.TestCase):
+  def setUp(self):
+    self.adb = _DummyAdb()
+
+  def ShellError(self):
+    def action(cmd):
+      raise ValueError('(device %s) command %r is not nice' % (self.adb, cmd))
+    return action
+
+  def get_answer(self):
+    logging.debug("called 'get_answer' of %r object", self)
+    return 42
+
+  def echo(self, thing):
+    logging.debug("called 'echo' of %r object", self)
+    return thing
+
+  def testCallTarget_succeds(self):
+    self.assertEquals(self.adb.Shell,
+                      self.call_target(self.call.adb.Shell))
+
+  def testCallTarget_failsExternal(self):
+    with self.assertRaises(ValueError):
+      self.call_target(mock.call.sys.getcwd)
+
+  def testCallTarget_failsUnknownAttribute(self):
+    with self.assertRaises(AttributeError):
+      self.call_target(self.call.adb.Run)
+
+  def testCallTarget_failsIntermediateCalls(self):
+    with self.assertRaises(AttributeError):
+      self.call_target(self.call.adb.RunShell('cmd').append)
+
+  def testPatchCall_method(self):
+    self.assertEquals(42, self.get_answer())
+    with self.patch_call(self.call.get_answer, return_value=123):
+      self.assertEquals(123, self.get_answer())
+    self.assertEquals(42, self.get_answer())
+
+  def testPatchCall_attribute_method(self):
+    with self.patch_call(self.call.adb.Shell, return_value='hello'):
+      self.assertEquals('hello', self.adb.Shell('echo hello'))
+
+  def testPatchCall_global(self):
+    with self.patch_call(mock.call.os.getcwd, return_value='/some/path'):
+      self.assertEquals('/some/path', os.getcwd())
+
+  def testPatchCall_withSideEffect(self):
+    with self.patch_call(self.call.adb.Shell, side_effect=ValueError):
+      with self.assertRaises(ValueError):
+        self.adb.Shell('echo hello')
+
+  def testAssertCalls_succeeds_simple(self):
+    self.assertEquals(42, self.get_answer())
+    with self.assertCall(self.call.get_answer(), 123):
+      self.assertEquals(123, self.get_answer())
+    self.assertEquals(42, self.get_answer())
+
+  def testAssertCalls_succeeds_multiple(self):
+    with self.assertCalls(
+        (mock.call.os.getcwd(), '/some/path'),
+        (self.call.echo('hello'), 'hello'),
+        (self.call.get_answer(), 11),
+        self.call.adb.Push('this_file', 'that_file'),
+        (self.call.get_answer(), 12)):
+      self.assertEquals(os.getcwd(), '/some/path')
+      self.assertEquals('hello', self.echo('hello'))
+      self.assertEquals(11, self.get_answer())
+      self.adb.Push('this_file', 'that_file')
+      self.assertEquals(12, self.get_answer())
+
+  def testAsserCalls_succeeds_withAction(self):
+    with self.assertCall(
+        self.call.adb.Shell('echo hello'), self.ShellError()):
+      with self.assertRaises(ValueError):
+        self.adb.Shell('echo hello')
+
+  def testAssertCalls_fails_tooManyCalls(self):
+    with self.assertRaises(AssertionError):
+      with self.assertCalls(self.call.adb.IsOnline()):
+        self.adb.IsOnline()
+        self.adb.IsOnline()
+
+  def testAssertCalls_fails_tooFewCalls(self):
+    with self.assertRaises(AssertionError):
+      with self.assertCalls(self.call.adb.IsOnline()):
+        pass
+
+  def testAssertCalls_succeeds_extraCalls(self):
+    # we are not watching Reboot, so the assertion succeeds
+    with self.assertCalls(self.call.adb.IsOnline()):
+      self.adb.IsOnline()
+      self.adb.Reboot()
+
+  def testAssertCalls_fails_extraCalls(self):
+    self.watchCalls([self.call.adb.Reboot])
+    # this time we are also watching Reboot, so the assertion fails
+    with self.assertRaises(AssertionError):
+      with self.assertCalls(self.call.adb.IsOnline()):
+        self.adb.IsOnline()
+        self.adb.Reboot()
+
+  def testAssertCalls_succeeds_NoCalls(self):
+    self.watchMethodCalls(self.call.adb) # we are watching all adb methods
+    with self.assertCalls():
+      pass
+
+  def testAssertCalls_fails_NoCalls(self):
+    self.watchMethodCalls(self.call.adb)
+    with self.assertRaises(AssertionError):
+      with self.assertCalls():
+        self.adb.IsOnline()
+
+
+if __name__ == '__main__':
+  logging.getLogger().setLevel(logging.DEBUG)
+  unittest.main(verbosity=2)
+
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index 2e39c3c..d5c9b35 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -64,6 +64,11 @@
   group.add_option('--build-directory', dest='build_directory',
                    help=('Path to the directory in which build files are'
                          ' located (should not include build type)'))
+  group.add_option('--output-directory', dest='output_directory',
+                   help=('Path to the directory in which build files are'
+                         ' located (must include build type). This will take'
+                         ' precedence over --debug, --release and'
+                         ' --build-directory'))
   group.add_option('--num_retries', dest='num_retries', type='int',
                    default=2,
                    help=('Number of retries for a test before '
@@ -95,6 +100,8 @@
   constants.SetBuildType(options.build_type)
   if options.build_directory:
     constants.SetBuildDirectory(options.build_directory)
+  if options.output_directory:
+    constants.SetOutputDirectort(options.output_directory)
   if options.environment not in constants.VALID_ENVIRONMENTS:
     error_func('--environment must be one of: %s' %
                ', '.join(constants.VALID_ENVIRONMENTS))