Repository: google/mobly

Issue:
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.


Discussion:

Resolving patch:
diff --git a/mobly/controllers/android_device.py b/mobly/controllers/android_device.py
--- a/mobly/controllers/android_device.py
+++ b/mobly/controllers/android_device.py
@@ -40,8 +40,7 @@
 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'
 
 
@@ -136,7 +135,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 +337,9 @@ def take_br(test_name, begin_time, ad):
 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 +394,21 @@ def set_logger_prefix_tag(self, tag):
         """
         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()
@@ -567,14 +557,14 @@ def load_snippet(self, name, package):
         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
@@ -829,13 +819,15 @@ def reboot(self):
             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/monsoon.py b/mobly/controllers/monsoon.py
--- a/mobly/controllers/monsoon.py
+++ b/mobly/controllers/monsoon.py
@@ -880,8 +880,7 @@ def execute_sequence_and_measure(self,
                 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 @@ def measure_power(self, hz, duration, tag, offset=30):
             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
--- 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


Test patch:
diff --git a/tests/mobly/test_runner_test.py b/tests/mobly/test_runner_test.py
--- a/tests/mobly/test_runner_test.py
+++ b/tests/mobly/test_runner_test.py
@@ -180,8 +180,7 @@ def test_run_two_test_classes(self, mock_get_all, mock_list_adb,
                      {"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),