diff --git a/README.md b/README.md
index f606ce5..16bb283 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,7 @@ class HelloWorldTest(base_test.BaseTestClass):
     # object is created from this.
     self.ads = self.register_controller(android_device)
     self.dut = self.ads[0]
+    self.dut.load_sl4a() # starts sl4a.
 
   def test_hello(self):
     self.dut.sl4a.makeToast('Hello World!')
@@ -147,6 +148,7 @@ class HelloWorldTest(base_test.BaseTestClass):
   def setup_class(self):
     self.ads = self.register_controller(android_device)
     self.dut = self.ads[0]
+    self.dut.load_sl4a()
 
   def test_hello(self):
     self.dut.sl4a.makeToast('Hello World!')
@@ -282,7 +284,9 @@ class HelloWorldTest(base_test.BaseTestClass):
     # requires at least two Android devices.
     self.ads = self.register_controller(android_device, min_number=2)
     self.dut = android_device.get_device(self.ads, label="dut")
+    self.dut.load_sl4a()
     self.discoverer = android_device.get_device(self.ads, label="discoverer")
+    self.discoverer.load_sl4a()
     self.dut.ed.clear_all_events()
     self.discoverer.ed.clear_all_events()
 
diff --git a/docs/tutorials/lesson1.md b/docs/tutorials/lesson1.md
index 3c93107..490ce40 100644
--- a/docs/tutorials/lesson1.md
+++ b/docs/tutorials/lesson1.md
@@ -40,6 +40,7 @@ class HelloWorldTest(base_test.BaseTestClass):
     # object is created from this.
     self.ads = self.register_controller(android_device)
     self.dut = self.ads[0]
+    self.dut.load_sl4a()
 
   def test_hello(self):
     self.dut.sl4a.makeToast('Hello World!')
diff --git a/docs/tutorials/lesson2.md b/docs/tutorials/lesson2.md
index 6d45715..83e76ec 100644
--- a/docs/tutorials/lesson2.md
+++ b/docs/tutorials/lesson2.md
@@ -21,6 +21,7 @@ class HelloWorldTest(base_test.BaseTestClass):
   def setup_class(self):
     self.ads = self.register_controller(android_device)
     self.dut = self.ads[0]
+    self.dut.load_sl4a()
 
   def test_hello(self):
     self.dut.sl4a.makeToast('Hello World!')
diff --git a/docs/tutorials/lesson5.md b/docs/tutorials/lesson5.md
index c31e000..7366f5e 100644
--- a/docs/tutorials/lesson5.md
+++ b/docs/tutorials/lesson5.md
@@ -41,7 +41,9 @@ class HelloWorldTest(base_test.BaseTestClass):
     # requires at least two Android devices.
     self.ads = self.register_controller(android_device, min_number=2)
     self.dut = android_device.get_device(self.ads, label="dut")
+    self.dut.load_sl4a()
     self.discoverer = android_device.get_device(self.ads, label="discoverer")
+    self.discoverer.load_sl4a()
     self.dut.ed.clear_all_events()
     self.discoverer.ed.clear_all_events()
 
diff --git a/mobly/controllers/android_device.py b/mobly/controllers/android_device.py
index 0d108a8..2f76b8a 100644
--- a/mobly/controllers/android_device.py
+++ b/mobly/controllers/android_device.py
@@ -40,8 +40,7 @@ ANDROID_DEVICE_ADB_LOGCAT_PARAM_KEY = 'adb_logcat_param'
 ANDROID_DEVICE_EMPTY_CONFIG_MSG = 'Configuration is empty, abort!'
 ANDROID_DEVICE_NOT_LIST_CONFIG_MSG = 'Configuration should be a list, abort!'
 
-# Keys for attributes in configs that alternate device behavior
-KEY_SKIP_SL4A = 'skip_sl4a'
+# Keys for attributes in configs that alternate the controller module behavior.
 KEY_DEVICE_REQUIRED = 'required'
 
 
@@ -98,10 +97,6 @@ def destroy(ads):
     for ad in ads:
         try:
             ad.stop_services()
-            # Refresh the adb connection to this device so we don't have any
-            # adb processes/ports started by the test lingering on after test
-            # stops. It also recovers unresponsive devices in some cases.
-            ad.adb.reconnect()
         except:
             ad.log.exception('Failed to clean up properly.')
 
@@ -136,7 +131,7 @@ def _start_services_on_ads(ads):
     for ad in ads:
         running_ads.append(ad)
         try:
-            ad.start_services(skip_sl4a=getattr(ad, KEY_SKIP_SL4A, False))
+            ad.start_services()
         except Exception as e:
             is_required = getattr(ad, KEY_DEVICE_REQUIRED, True)
             if is_required:
@@ -338,10 +333,9 @@ def take_bug_reports(ads, test_name, begin_time):
 class AndroidDevice(object):
     """Class representing an android device.
 
-    Each object of this class represents one Android device in Mobly, including
-    handles to adb, fastboot, and sl4a clients. In addition to direct adb
-    commands, this object also uses adb port forwarding to talk to the Android
-    device.
+    Each object of this class represents one Android device in Mobly. This class
+    provides various ways, like adb, fastboot, sl4a, and snippets, to control an
+    Android device, whether it's a real device or an emulator instance.
 
     Attributes:
         serial: A string that's the serial number of the Androi device.
@@ -396,29 +390,21 @@ class AndroidDevice(object):
         """
         self.log.extra['tag'] = tag
 
-    # TODO(angli): This function shall be refactored to accommodate all services
-    # and not have hard coded switch for SL4A when b/29157104 is done.
-    def start_services(self, skip_sl4a=False):
-        """Starts long running services on the android device.
-
-        1. Start adb logcat capture.
-        2. Start SL4A if not skipped.
-
-        Args:
-            skip_sl4a: Does not attempt to start SL4A if True.
+    def start_services(self):
+        """Starts long running services on the android device, like adb logcat
+        capture.
         """
         try:
             self.start_adb_logcat()
         except:
             self.log.exception('Failed to start adb logcat!')
             raise
-        if not skip_sl4a:
-            self._start_sl4a()
 
     def stop_services(self):
-        """Stops long running services on the android device.
+        """Stops long running services on the Android device.
 
-        Stop adb logcat and terminate sl4a sessions if exist.
+        Stop adb logcat, terminate sl4a sessions if exist, terminate all
+        snippet clients.
         """
         if self._adb_logcat_process:
             self.stop_adb_logcat()
@@ -558,60 +544,57 @@ class AndroidDevice(object):
                     'Snippet package "%s" has already been loaded under name'
                     ' "%s".' % (package, client_name))
         host_port = utils.get_available_host_port()
-        # TODO(adorokhine): Don't assume that a free host-side port is free on
-        # the device as well. Both sides should allocate a unique port.
-        device_port = host_port
         client = snippet_client.SnippetClient(
-            package=package, port=host_port, adb_proxy=self.adb)
-        self._start_jsonrpc_client(client, host_port, device_port)
+            package=package, host_port=host_port, adb_proxy=self.adb)
+        self._start_jsonrpc_client(client)
         self._snippet_clients[name] = client
         setattr(self, name, client)
 
-    def _start_sl4a(self):
-        """Create an sl4a connection to the device.
+    def load_sl4a(self):
+        """Start sl4a service on the Android device.
 
-        Assigns the open sl4a client to self.sl4a. By default, another
-        connection on the same session is made for EventDispatcher, and the
-        dispatcher is bound to self.ed.
+        Launch sl4a server if not already running, spin up a session on the
+        server, and two connections to this session.
 
-        If sl4a server is not started on the device, tries to start it.
+        Creates an sl4a client (self.sl4a) with one connection, and one
+        EventDispatcher obj (self.ed) with the other connection.
         """
         host_port = utils.get_available_host_port()
-        device_port = sl4a_client.DEVICE_SIDE_PORT
-        self.sl4a = sl4a_client.Sl4aClient(self.adb)
-        self._start_jsonrpc_client(self.sl4a, host_port, device_port)
+        self.sl4a = sl4a_client.Sl4aClient(
+            host_port=host_port, adb_proxy=self.adb)
+        self._start_jsonrpc_client(self.sl4a)
 
         # Start an EventDispatcher for the current sl4a session
-        event_client = sl4a_client.Sl4aClient(self.adb)
+        event_client = sl4a_client.Sl4aClient(
+            host_port=host_port, adb_proxy=self.adb)
         event_client.connect(
-            port=host_port,
             uid=self.sl4a.uid,
             cmd=jsonrpc_client_base.JsonRpcCommand.CONTINUE)
         self.ed = event_dispatcher.EventDispatcher(event_client)
         self.ed.start()
 
-    def _start_jsonrpc_client(self, client, host_port, device_port):
+    def _start_jsonrpc_client(self, client):
         """Create a connection to a jsonrpc server running on the device.
 
         If the connection cannot be made, tries to restart it.
         """
         client.check_app_installed()
-        self.adb.tcp_forward(host_port, device_port)
+        self.adb.tcp_forward(client.host_port, client.device_port)
         try:
-            client.connect(port=host_port)
+            client.connect()
         except:
             try:
                 client.stop_app()
             except Exception as e:
                 self.log.warning(e)
             client.start_app()
-            client.connect(port=host_port)
+            client.connect()
 
     def _terminate_jsonrpc_client(self, client):
         client.closeSl4aSession()
         client.close()
         client.stop_app()
-        self.adb.forward('--remove tcp:%d' % client.port)
+        self.adb.forward('--remove tcp:%d' % client.host_port)
 
     def _is_timestamp_in_range(self, target, begin_time, end_time):
         low = mobly_logger.logline_timestamp_comparator(begin_time,
@@ -829,13 +812,15 @@ class AndroidDevice(object):
             self.fastboot.reboot()
             return
         snippet_info = self._get_active_snippet_info()
+        use_sl4a = self.sl4a is not None
         self.stop_services()
         self.adb.reboot()
         self.wait_for_boot_completion()
         if self.is_rootable:
             self.root_adb()
-        skip_sl4a = getattr(self, KEY_SKIP_SL4A, False) or self.sl4a is None
-        self.start_services(skip_sl4a=skip_sl4a)
+        self.start_services()
+        if use_sl4a:
+            self.load_sl4a()
         for attr_name, package_name in snippet_info:
             self.load_snippet(attr_name, package_name)
 
diff --git a/mobly/controllers/android_device_lib/jsonrpc_client_base.py b/mobly/controllers/android_device_lib/jsonrpc_client_base.py
index 3f7d6f5..da16a3e 100644
--- a/mobly/controllers/android_device_lib/jsonrpc_client_base.py
+++ b/mobly/controllers/android_device_lib/jsonrpc_client_base.py
@@ -39,9 +39,7 @@ Response:
 from builtins import str
 
 import json
-import logging
 import socket
-import sys
 import threading
 import time
 
@@ -94,15 +92,25 @@ class JsonRpcClientBase(object):
     of communication.
 
     Attributes:
-        uid: (int) The uid of this session.
+        host_port: (int) The host port of this RPC client.
+        device_port: (int) The device port of this RPC client.
         app_name: (str) The user-visible name of the app being communicated
-          with. Must be set by the superclass.
+                  with.
+        uid: (int) The uid of this session.
     """
-    def __init__(self, adb_proxy):
+
+    def __init__(self, host_port, device_port, app_name, adb_proxy):
         """
         Args:
-            adb_proxy: adb.AdbProxy, The adb proxy to use to start the app
+            host_port: (int) The host port of this RPC client.
+            device_port: (int) The device port of this RPC client.
+            app_name: (str) The user-visible name of the app being communicated
+                      with.
+            adb_proxy: (adb.AdbProxy) The adb proxy to use to start the app.
         """
+        self.host_port = host_port
+        self.device_port = device_port
+        self.app_name = app_name
         self.uid = None
         self._adb = adb_proxy
         self._client = None  # prevent close errors on connect failure
@@ -139,16 +147,6 @@ class JsonRpcClientBase(object):
         """
         raise NotImplementedError()
 
-    def _is_app_running(self):
-        """Checks if the app is currently running on an android device.
-
-        Must be implemented by subclasses.
-
-        Returns:
-            True if the app is running, False otherwise.
-        """
-        raise NotImplementedError()
-
     # Rest of the client methods.
 
     def check_app_installed(self):
@@ -161,7 +159,7 @@ class JsonRpcClientBase(object):
 
         Args:
             wait_time: float, The time to wait for the app to come up before
-                raising an error.
+                       raising an error.
 
         Raises:
             AppStartError: When the app was not able to be started.
@@ -175,8 +173,7 @@ class JsonRpcClientBase(object):
         raise AppStartError(
             '%s failed to start on %s.' % (self.app_name, self._adb.serial))
 
-    def connect(self, port, addr='localhost', uid=UNKNOWN_UID,
-                cmd=JsonRpcCommand.INIT):
+    def connect(self, uid=UNKNOWN_UID, cmd=JsonRpcCommand.INIT):
         """Opens a connection to a JSON RPC server.
 
         Opens a connection to a remote client. The connection attempt will time
@@ -185,12 +182,8 @@ class JsonRpcClientBase(object):
         as well.
 
         Args:
-            port: int, The port this client should connect to.
-            addr: str, The address this client should connect to.
             uid: int, The uid of the session to join, or UNKNOWN_UID to start a
                  new session.
-            connection_timeout: int, The time to wait for the connection to come
-                 up.
             cmd: JsonRpcCommand, The command to use for creating the connection.
 
         Raises:
@@ -199,14 +192,9 @@ class JsonRpcClientBase(object):
             ProtocolError: Raised when there is an error in the protocol.
         """
         self._counter = self._id_counter()
-        try:
-            self._conn = socket.create_connection((addr, port),
-                                                  _SOCKET_TIMEOUT)
-            self._conn.settimeout(_SOCKET_TIMEOUT)
-        except (socket.timeout, socket.error, IOError):
-            logging.exception("Failed to create socket connection!")
-            raise
-        self.port = port
+        self._conn = socket.create_connection(('127.0.0.1', self.host_port),
+                                              _SOCKET_TIMEOUT)
+        self._conn.settimeout(_SOCKET_TIMEOUT)
         self._client = self._conn.makefile(mode="brw")
 
         resp = self._cmd(cmd, uid)
@@ -271,6 +259,20 @@ class JsonRpcClientBase(object):
             raise ProtocolError(ProtocolError.MISMATCHED_API_ID)
         return result['result']
 
+    def _is_app_running(self):
+        """Checks if the app is currently running on an android device.
+
+        May be overridden by subclasses with custom sanity checks.
+        """
+        running = False
+        try:
+          self.connect()
+          running = True
+        finally:
+          self.close()
+          # This 'return' squashes exceptions from connect()
+          return running
+
     def __getattr__(self, name):
         """Wrapper for python magic to turn method calls into RPC calls."""
         def rpc_call(*args):
diff --git a/mobly/controllers/android_device_lib/sl4a_client.py b/mobly/controllers/android_device_lib/sl4a_client.py
index 1842eb5..71963d0 100644
--- a/mobly/controllers/android_device_lib/sl4a_client.py
+++ b/mobly/controllers/android_device_lib/sl4a_client.py
@@ -27,13 +27,25 @@ _LAUNCH_CMD = (
 
 
 class Sl4aClient(jsonrpc_client_base.JsonRpcClientBase):
-    def __init__(self, adb_proxy):
-        super(Sl4aClient, self).__init__(adb_proxy)
-        self.app_name = 'SL4A'
+    """A client for interacting with SL4A using Mobly Snippet Lib.
+
+    See superclass documentation for a list of public attributes.
+    """
+
+    def __init__(self, host_port, adb_proxy):
+        """Initializes an Sl4aClient.
+
+        Args:
+            host_port: (int) The host port of this RPC client.
+            adb_proxy: (adb.AdbProxy) The adb proxy to use to start the app.
+        """
+        super(Sl4aClient, self).__init__(
+            host_port=host_port, device_port=DEVICE_SIDE_PORT, app_name='SL4A',
+            adb_proxy=adb_proxy)
 
     def _do_start_app(self):
         """Overrides superclass."""
-        self._adb.shell(_LAUNCH_CMD.format(DEVICE_SIDE_PORT))
+        self._adb.shell(_LAUNCH_CMD.format(self.device_port))
 
     def stop_app(self):
         """Overrides superclass."""
@@ -49,16 +61,3 @@ class Sl4aClient(jsonrpc_client_base.JsonRpcClientBase):
             if (e.ret_code == 1) and (not e.stdout) and (not e.stderr):
                 return False
             raise
-
-    def _is_app_running(self):
-        """Overrides superclass."""
-        # Grep for process with a preceding S which means it is truly started.
-        try:
-            out = self._adb.shell(
-                'ps | grep "S com.googlecode.android_scripting"').decode(
-                    'utf-8').strip()
-            return bool(out)
-        except adb.AdbError as e:
-            if (e.ret_code == 1) and (not e.stdout) and (not e.stderr):
-                return False
-            raise
diff --git a/mobly/controllers/android_device_lib/snippet_client.py b/mobly/controllers/android_device_lib/snippet_client.py
index 370d852..ebcae0f 100644
--- a/mobly/controllers/android_device_lib/snippet_client.py
+++ b/mobly/controllers/android_device_lib/snippet_client.py
@@ -15,7 +15,6 @@
 # limitations under the License.
 """JSON RPC interface to Mobly Snippet Lib."""
 import logging
-import socket
 
 from mobly.controllers.android_device_lib import adb
 from mobly.controllers.android_device_lib import jsonrpc_client_base
@@ -32,42 +31,43 @@ class Error(Exception):
 
 
 class SnippetClient(jsonrpc_client_base.JsonRpcClientBase):
-    def __init__(self, package, port, adb_proxy):
-        """Initialzies a SnippetClient.
+    """A client for interacting with snippet APKs using Mobly Snippet Lib.
+
+    See superclass documentation for a list of public attributes.
+    """
+
+    def __init__(self, package, host_port, adb_proxy):
+        """Initializes a SnippetClient.
   
         Args:
-          package: (str) The package name of the apk where the snippets are
-            defined.
-          port: (int) The port at which to start the snippet client. Note that
-            the same port will currently be used for both the device and host
-            side of the connection.
-            TODO(adorokhine): allocate a distinct free port for both sides of
-            the connection; it is not safe in general to reuse the same one.
-          adb_proxy: (adb.AdbProxy) The adb proxy to use to start the app.
+            package: (str) The package name of the apk where the snippets are
+                     defined.
+            host_port: (int) The port at which to start the snippet client. Note
+                       that the same port will currently be used for both the
+                       device and host side of the connection.
+            adb_proxy: (adb.AdbProxy) The adb proxy to use to start the app.
         """
-        super(SnippetClient, self).__init__(adb_proxy)
-        self.app_name = package
-        self._package = package
-        self._port = port
-
-    @property
-    def package(self):
-        return self._package
+        # TODO(adorokhine): Don't assume that a free host-side port is free on
+        # the device as well. Both sides should allocate a unique port.
+        super(SnippetClient, self).__init__(
+            host_port=host_port, device_port=host_port, app_name=package,
+            adb_proxy=adb_proxy)
+        self.package = package
 
     def _do_start_app(self):
         """Overrides superclass."""
-        cmd = _LAUNCH_CMD.format(self._port, self._package)
+        cmd = _LAUNCH_CMD.format(self.device_port, self.package)
         # Use info here so people know exactly what's happening here, which is
         # helpful since they need to create their own instrumentations and
         # manifest.
         logging.info('Launching snippet apk with: %s', cmd)
-        self._adb.shell(_LAUNCH_CMD.format(self._port, self._package))
+        self._adb.shell(cmd)
 
     def stop_app(self):
         """Overrides superclass."""
-        cmd = _STOP_CMD.format(self._package)
+        cmd = _STOP_CMD.format(self.package)
         logging.info('Stopping snippet apk with: %s', cmd)
-        out = self._adb.shell(_STOP_CMD.format(self._package)).decode('utf-8')
+        out = self._adb.shell(_STOP_CMD.format(self.package)).decode('utf-8')
         if 'OK (0 tests)' not in out:
             raise Error('Failed to stop existing apk. Unexpected output: %s' %
                         out)
@@ -77,22 +77,9 @@ class SnippetClient(jsonrpc_client_base.JsonRpcClientBase):
         try:
             out = self._adb.shell(
                 'pm list instrumentation | grep ^instrumentation:%s/' %
-                self._package).decode('utf-8')
+                self.package).decode('utf-8')
             return bool(out)
         except adb.AdbError as e:
             if (e.ret_code == 1) and (not e.stdout) and (not e.stderr):
                 return False
             raise
-
-    def _is_app_running(self):
-        """Overrides superclass."""
-        # While instrumentation is running, 'ps' only shows the package of the
-        # main apk. However, the main apk might be running for other reasons.
-        # Instead of grepping the process tree, this is implemented by seeing if
-        # our destination port is alive.
-        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        try:
-            result = sock.connect_ex(('127.0.0.1', self._port))
-            return result == 0
-        finally:
-            sock.close()
diff --git a/mobly/controllers/monsoon.py b/mobly/controllers/monsoon.py
index e02c22b..17d945c 100644
--- a/mobly/controllers/monsoon.py
+++ b/mobly/controllers/monsoon.py
@@ -880,8 +880,7 @@ class Monsoon(object):
                 self._wait_for_device(self.dut)
                 # Wait for device to come back online.
                 time.sleep(10)
-                self.dut.start_services(skip_sl4a=getattr(self.dut,
-                                                          "skip_sl4a", False))
+                self.dut.start_services()
                 # Release wake lock to put device into sleep.
                 self.dut.sl4a.goToSleepNow()
         return results
@@ -923,8 +922,7 @@ class Monsoon(object):
             self._wait_for_device(self.dut)
             # Wait for device to come back online.
             time.sleep(10)
-            self.dut.start_services(skip_sl4a=getattr(self.dut,
-                                                      "skip_sl4a", False))
+            self.dut.start_services()
             # Release wake lock to put device into sleep.
             self.dut.sl4a.goToSleepNow()
             self.log.info("Dut reconnected.")
diff --git a/tools/sl4a_shell.py b/tools/sl4a_shell.py
index 13b4847..501e656 100755
--- a/tools/sl4a_shell.py
+++ b/tools/sl4a_shell.py
@@ -43,6 +43,7 @@ class Sl4aShell(jsonrpc_shell_base.JsonRpcShellBase):
     def _start_services(self, console_env):
         """Overrides superclass."""
         self._ad.start_services()
+        self._ad.load_sl4a()
         console_env['s'] = self._ad.sl4a
         console_env['sl4a'] = self._ad.sl4a
         console_env['ed'] = self._ad.ed
diff --git a/tools/snippet_shell.py b/tools/snippet_shell.py
index 0283f30..0991747 100755
--- a/tools/snippet_shell.py
+++ b/tools/snippet_shell.py
@@ -28,7 +28,6 @@ from __future__ import print_function
 
 import argparse
 import logging
-import sys
 
 from mobly.controllers.android_device_lib import jsonrpc_shell_base
 

Make sl4a a service that needs to be explicitly requested
Right now people have to explicitly say they don't need sl4a in config file, which is odd since most users don't use sl4a.

Let's flip the assumption and make sl4a an optional service that people can request in setup if needed.
diff --git a/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py b/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py
index ce01bad..19dc109 100755
--- a/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py
+++ b/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py
@@ -46,7 +46,9 @@ class MockSocketFile(object):
 
 class FakeRpcClient(jsonrpc_client_base.JsonRpcClientBase):
     def __init__(self):
-      super(FakeRpcClient, self).__init__(adb_proxy=None)
+        super(FakeRpcClient, self).__init__(
+            host_port=80, device_port=90, app_name='FakeRpcClient',
+            adb_proxy=None)
 
 
 class JsonRpcClientBaseTest(unittest.TestCase):
@@ -78,7 +80,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         mock_create_connection.side_effect = IOError()
         with self.assertRaises(IOError):
             client = FakeRpcClient()
-            client.connect(port=80)
+            client.connect()
 
     @mock.patch('socket.create_connection')
     def test_connect_timeout(self, mock_create_connection):
@@ -90,7 +92,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         mock_create_connection.side_effect = socket.timeout
         with self.assertRaises(socket.timeout):
             client = FakeRpcClient()
-            client.connect(port=80)
+            client.connect()
 
     @mock.patch('socket.create_connection')
     def test_handshake_error(self, mock_create_connection):
@@ -104,7 +106,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         mock_create_connection.return_value = fake_conn
         with self.assertRaises(jsonrpc_client_base.ProtocolError):
             client = FakeRpcClient()
-            client.connect(port=80)
+            client.connect()
 
     @mock.patch('socket.create_connection')
     def test_connect_handshake(self, mock_create_connection):
@@ -118,7 +120,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         mock_create_connection.return_value = fake_conn
 
         client = FakeRpcClient()
-        client.connect(port=80)
+        client.connect()
 
         self.assertEqual(client.uid, 1)
 
@@ -135,7 +137,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         mock_create_connection.return_value = fake_conn
 
         client = FakeRpcClient()
-        client.connect(port=80)
+        client.connect()
 
         self.assertEqual(client.uid, jsonrpc_client_base.UNKNOWN_UID)
 
@@ -149,7 +151,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         fake_file = self.setup_mock_socket_file(mock_create_connection)
 
         client = FakeRpcClient()
-        client.connect(port=80)
+        client.connect()
 
         fake_file.resp = None
 
@@ -169,7 +171,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         fake_file = self.setup_mock_socket_file(mock_create_connection)
 
         client = FakeRpcClient()
-        client.connect(port=80)
+        client.connect()
 
         fake_file.resp = MOCK_RESP_WITH_ERROR
 
@@ -186,7 +188,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         fake_file = self.setup_mock_socket_file(mock_create_connection)
 
         client = FakeRpcClient()
-        client.connect(port=80)
+        client.connect()
 
         fake_file.resp = (MOCK_RESP_TEMPLATE % 52).encode('utf8')
 
@@ -205,7 +207,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         fake_file = self.setup_mock_socket_file(mock_create_connection)
 
         client = FakeRpcClient()
-        client.connect(port=80)
+        client.connect()
 
         fake_file.resp = None
 
@@ -224,7 +226,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         fake_file = self.setup_mock_socket_file(mock_create_connection)
 
         client = FakeRpcClient()
-        client.connect(port=80)
+        client.connect()
 
         result = client.some_rpc(1, 2, 3)
         self.assertEquals(result, 123)
@@ -243,7 +245,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         fake_file = self.setup_mock_socket_file(mock_create_connection)
 
         client = FakeRpcClient()
-        client.connect(port=80)
+        client.connect()
 
         for i in range(0, 10):
             fake_file.resp = (MOCK_RESP_TEMPLATE % i).encode('utf-8')
diff --git a/tests/mobly/test_runner_test.py b/tests/mobly/test_runner_test.py
index 74ffb8d..ed166d3 100755
--- a/tests/mobly/test_runner_test.py
+++ b/tests/mobly/test_runner_test.py
@@ -180,8 +180,7 @@ class TestRunnerTest(unittest.TestCase):
                      {"serial": "xxxx", "magic": "Magic2"}]
         mock_test_config[tb_key][mock_ctrlr_config_name] = my_config
         mock_test_config[tb_key]["AndroidDevice"] = [
-            {"serial": "1",
-             "skip_sl4a": True}
+            {"serial": "1"}
         ]
         tr = test_runner.TestRunner(mock_test_config,
                                     [('Integration2Test', None),

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup==1.2.2
future==1.0.0
iniconfig==2.1.0
-e git+https://github.com/google/mobly.git@312070ed24d3e229689a374c7357267879fd2549#egg=mobly
mock==1.0.1
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
pytz==2025.2
tomli==2.2.1

name: mobly
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - future==1.0.0
      - iniconfig==2.1.0
      - mock==1.0.1
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytz==2025.2
      - tomli==2.2.1
prefix: /opt/conda/envs/mobly

tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_connect_handshake tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_connect_handshake_unknown_status tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_connect_no_response tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_connect_timeout tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_handshake_error tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_open_timeout_io_error tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_call_increment_counter tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_error_response tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_id_mismatch tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_no_response tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_send_to_socket
tests/mobly/test_runner_test.py::TestRunnerTest::test_register_controller_dup_register tests/mobly/test_runner_test.py::TestRunnerTest::test_register_controller_less_than_min_number tests/mobly/test_runner_test.py::TestRunnerTest::test_register_controller_no_config tests/mobly/test_runner_test.py::TestRunnerTest::test_register_controller_no_get_info tests/mobly/test_runner_test.py::TestRunnerTest::test_register_controller_return_value tests/mobly/test_runner_test.py::TestRunnerTest::test_run_twice tests/mobly/test_runner_test.py::TestRunnerTest::test_run_two_test_classes tests/mobly/test_runner_test.py::TestRunnerTest::test_verify_controller_module tests/mobly/test_runner_test.py::TestRunnerTest::test_verify_controller_module_missing_attr tests/mobly/test_runner_test.py::TestRunnerTest::test_verify_controller_module_null_attr
tools/sl4a_shell.py mobly/controllers/android_device_lib/snippet_client.py docs/tutorials/lesson1.md mobly/controllers/android_device.py tools/snippet_shell.py mobly/controllers/android_device_lib/jsonrpc_client_base.py docs/tutorials/lesson5.md mobly/controllers/monsoon.py README.md docs/tutorials/lesson2.md mobly/controllers/android_device_lib/sl4a_client.py
tools/sl4a_shell.py mobly/controllers/android_device_lib/snippet_client.py docs/tutorials/lesson1.md mobly/controllers/android_device.py tools/snippet_shell.py mobly/controllers/android_device_lib/jsonrpc_client_base.py docs/tutorials/lesson5.md mobly/controllers/monsoon.py README.md docs/tutorials/lesson2.md mobly/controllers/android_device_lib/sl4a_client.py