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()