Properly handle service re-start and resume in `AndroidDevice`. (#662)

Only re-start and resume the services that were alive before the reboot/disconnect.
diff --git a/mobly/controllers/android_device.py b/mobly/controllers/android_device.py
index f9122ef..7557ae2 100644
--- a/mobly/controllers/android_device.py
+++ b/mobly/controllers/android_device.py
@@ -649,6 +649,7 @@
 
         For sample usage, see self.reboot().
         """
+        live_service_names = self.services.list_live_services()
         self.services.stop_all()
         # On rooted devices, system properties may change on reboot, so disable
         # the `build_info` cache by setting `_is_rebooting` to True and
@@ -676,7 +677,7 @@
             self._is_rebooting = False
             if self.is_rootable:
                 self.root_adb()
-        self.services.start_all()
+        self.services.start_services(live_service_names)
 
     @contextlib.contextmanager
     def handle_usb_disconnect(self):
@@ -725,11 +726,12 @@
                   # context
                   ad.adb.wait_for_device(timeout=SOME_TIMEOUT)
         """
+        live_service_names = self.services.list_live_services()
         self.services.pause_all()
         try:
             yield
         finally:
-            self.services.resume_all()
+            self.services.resume_services(live_service_names)
 
     @property
     def build_info(self):
diff --git a/mobly/controllers/android_device_lib/service_manager.py b/mobly/controllers/android_device_lib/service_manager.py
index 11b0ab8..710e133 100644
--- a/mobly/controllers/android_device_lib/service_manager.py
+++ b/mobly/controllers/android_device_lib/service_manager.py
@@ -15,6 +15,7 @@
 # TODO(xpconanfan: move the device errors to a more generic location so
 # other device controllers like iOS can share it.
 import collections
+import contextlib
 import inspect
 
 from mobly import expects
@@ -118,6 +119,20 @@
                 (func.__name__, alias)):
                 func(self._service_objects[alias])
 
+    def list_live_services(self):
+        """Lists the aliases of all the services that are alive.
+
+        Order of this list is determined by the order the services are
+        registered in.
+
+        Returns:
+            list of strings, the aliases of the services that are running.
+        """
+        aliases = []
+        self.for_each(lambda service: aliases.append(service.alias)
+                      if service.is_alive else None)
+        return aliases
+
     def create_output_excerpts_all(self, test_info):
         """Creates output excerpts from all services.
 
@@ -134,6 +149,8 @@
         excerpt_paths = {}
 
         def create_output_excerpts_for_one(service):
+            if not service.is_alive:
+                return
             paths = service.create_output_excerpts(test_info)
             excerpt_paths[service.alias] = paths
 
@@ -160,6 +177,25 @@
                                               alias):
                     service.start()
 
+    def start_services(self, service_alises):
+        """Starts the specified services.
+
+        Services will be started in the order specified by the input list.
+        No-op for services that are already running.
+
+        Args:
+            service_alises: list of strings, the aliases of services to start.
+        """
+        for name in service_alises:
+            if name not in self._service_objects:
+                raise Error(
+                    self._device,
+                    'No service is registered under the name "%s", cannot start.'
+                    % name)
+            service = self._service_objects[name]
+            if not service.is_alive:
+                service.start()
+
     def stop_all(self):
         """Stops all active service instances.
 
@@ -195,6 +231,25 @@
                                           alias):
                 service.resume()
 
+    def resume_services(self, service_alises):
+        """Resumes the specified services.
+
+        Services will be resumed in the order specified by the input list.
+        No-op for services that are already running.
+
+        Args:
+            service_alises: list of strings, the names of services to start.
+        """
+        for name in service_alises:
+            if name not in self._service_objects:
+                raise Error(
+                    self._device,
+                    'No service is registered under the name "%s", cannot resume.'
+                    % name)
+            service = self._service_objects[name]
+            if not service.is_alive:
+                service.resume()
+
     def __getattr__(self, name):
         """Syntactic sugar to enable direct access of service objects by alias.
 
diff --git a/tests/mobly/controllers/android_device_lib/service_manager_test.py b/tests/mobly/controllers/android_device_lib/service_manager_test.py
index 6e24505..c989e83 100755
--- a/tests/mobly/controllers/android_device_lib/service_manager_test.py
+++ b/tests/mobly/controllers/android_device_lib/service_manager_test.py
@@ -46,9 +46,11 @@
 
     def pause(self):
         self.pause_func()
+        self._alive = False
 
     def resume(self):
         self.resume_func()
+        self._alive = True
 
 
 class ServiceManagerTest(unittest.TestCase):
@@ -424,6 +426,49 @@
         self.assert_recorded_one_error(
             'Failed to resume service "mock_service1".')
 
+    def test_list_live_services(self):
+        manager = service_manager.ServiceManager(mock.MagicMock())
+        manager.register('mock_service1', MockService, start_service=False)
+        manager.register('mock_service2', MockService)
+        aliases = manager.list_live_services()
+        self.assertEqual(aliases, ['mock_service2'])
+        manager.stop_all()
+        aliases = manager.list_live_services()
+        self.assertEqual(aliases, [])
+
+    def test_start_services(self):
+        manager = service_manager.ServiceManager(mock.MagicMock())
+        manager.register('mock_service1', MockService, start_service=False)
+        manager.register('mock_service2', MockService, start_service=False)
+        manager.start_services(['mock_service2'])
+        aliases = manager.list_live_services()
+        self.assertEqual(aliases, ['mock_service2'])
+
+    def test_start_services_non_existent(self):
+        manager = service_manager.ServiceManager(mock.MagicMock())
+        msg = ('.* No service is registered under the name "mock_service", '
+               'cannot start.')
+        with self.assertRaisesRegex(service_manager.Error, msg):
+            manager.start_services(['mock_service'])
+
+    def test_resume_services(self):
+        manager = service_manager.ServiceManager(mock.MagicMock())
+        manager.register('mock_service1', MockService)
+        manager.register('mock_service2', MockService)
+        manager.pause_all()
+        aliases = manager.list_live_services()
+        self.assertEqual(aliases, [])
+        manager.resume_services(['mock_service2'])
+        aliases = manager.list_live_services()
+        self.assertEqual(aliases, ['mock_service2'])
+
+    def test_resume_services_non_existent(self):
+        manager = service_manager.ServiceManager(mock.MagicMock())
+        msg = ('.* No service is registered under the name "mock_service", '
+               'cannot resume.')
+        with self.assertRaisesRegex(service_manager.Error, msg):
+            manager.resume_services(['mock_service'])
+
 
 if __name__ == '__main__':
     unittest.main()