prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Refactor Snippet Client
  Mobly supports testing devices of multiple platforms, including Android, iOS and Windows. Mobly has one snippet client for each platform, and these clients share common patterns. So we want to consolidate current clients that they inherit from the same base class.
  
  Currently, the father class of Android snippet client has too much Android-specific code, so it's better to add a new general base class. We will add a base class and one client V2 for each platform and we won't modify existing clients.
  
  This issue is also a prerequisite of making iOS related code open source.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `mobly/controllers/android_device_lib/services/snippet_management_service.py`
  Content:
  ```
  1 # Copyright 2018 Google Inc.
  2 #
  3 # Licensed under the Apache License, Version 2.0 (the "License");
  4 # you may not use this file except in compliance with the License.
  5 # You may obtain a copy of the License at
  6 #
  7 #     http://www.apache.org/licenses/LICENSE-2.0
  8 #
  9 # Unless required by applicable law or agreed to in writing, software
  10 # distributed under the License is distributed on an "AS IS" BASIS,
  11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12 # See the License for the specific language governing permissions and
  13 # limitations under the License.
  14 """Module for the snippet management service."""
  15 from mobly.controllers.android_device_lib import errors
  16 from mobly.controllers.android_device_lib import snippet_client_v2
  17 from mobly.controllers.android_device_lib.services import base_service
  18 
  19 MISSING_SNIPPET_CLIENT_MSG = 'No snippet client is registered with name "%s".'
  20 
  21 # This config is transient and we will remove it after completing the migration
  22 # from v1 to v2.
  23 _CLIENT_V2_CONFIG_KEY = 'use_mobly_snippet_client_v2'
  24 
  25 
  26 class Error(errors.ServiceError):
  27   """Root error type for snippet management service."""
  28   SERVICE_TYPE = 'SnippetManagementService'
  29 
  30 
  31 class SnippetManagementService(base_service.BaseService):
  32   """Management service of snippet clients.
  33 
  34   This service manages all the snippet clients associated with an Android
  35   device.
  36   """
  37 
  38   def __init__(self, device, configs=None):
  39     del configs  # Unused param.
  40     self._device = device
  41     self._is_alive = False
  42     self._snippet_clients = {}
  43     super().__init__(device)
  44 
  45   @property
  46   def is_alive(self):
  47     """True if any client is running, False otherwise."""
  48     return any([client.is_alive for client in self._snippet_clients.values()])
  49 
  50   def get_snippet_client(self, name):
  51     """Gets the snippet client managed under a given name.
  52 
  53     Args:
  54       name: string, the name of the snippet client under management.
  55 
  56     Returns:
  57       SnippetClient.
  58     """
  59     if name in self._snippet_clients:
  60       return self._snippet_clients[name]
  61 
  62   def add_snippet_client(self, name, package):
  63     """Adds a snippet client to the management.
  64 
  65     Args:
  66       name: string, the attribute name to which to attach the snippet
  67         client. E.g. `name='maps'` attaches the snippet client to
  68         `ad.maps`.
  69       package: string, the package name of the snippet apk to connect to.
  70 
  71     Raises:
  72       Error, if a duplicated name or package is passed in.
  73     """
  74     # Should not load snippet with the same name more than once.
  75     if name in self._snippet_clients:
  76       raise Error(
  77           self, 'Name "%s" is already registered with package "%s", it cannot '
  78           'be used again.' % (name, self._snippet_clients[name].client.package))
  79     # Should not load the same snippet package more than once.
  80     for snippet_name, client in self._snippet_clients.items():
  81       if package == client.package:
  82         raise Error(
  83             self, 'Snippet package "%s" has already been loaded under name'
  84             ' "%s".' % (package, snippet_name))
  85 
  86     client = snippet_client_v2.SnippetClientV2(package=package, ad=self._device)
  87     client.initialize()
  88     self._snippet_clients[name] = client
  89 
  90   def remove_snippet_client(self, name):
  91     """Removes a snippet client from management.
  92 
  93     Args:
  94       name: string, the name of the snippet client to remove.
  95 
  96     Raises:
  97       Error: if no snippet client is managed under the specified name.
  98     """
  99     if name not in self._snippet_clients:
  100       raise Error(self._device, MISSING_SNIPPET_CLIENT_MSG % name)
  101     client = self._snippet_clients.pop(name)
  102     client.stop()
  103 
  104   def start(self):
  105     """Starts all the snippet clients under management."""
  106     for client in self._snippet_clients.values():
  107       if not client.is_alive:
  108         self._device.log.debug('Starting SnippetClient<%s>.', client.package)
  109         client.initialize()
  110       else:
  111         self._device.log.debug(
  112             'Not startng SnippetClient<%s> because it is already alive.',
  113             client.package)
  114 
  115   def stop(self):
  116     """Stops all the snippet clients under management."""
  117     for client in self._snippet_clients.values():
  118       if client.is_alive:
  119         self._device.log.debug('Stopping SnippetClient<%s>.', client.package)
  120         client.stop()
  121       else:
  122         self._device.log.debug(
  123             'Not stopping SnippetClient<%s> because it is not alive.',
  124             client.package)
  125 
  126   def pause(self):
  127     """Pauses all the snippet clients under management.
  128 
  129     This clears the host port of a client because a new port will be
  130     allocated in `resume`.
  131     """
  132     for client in self._snippet_clients.values():
  133       self._device.log.debug('Pausing SnippetClient<%s>.', client.package)
  134       client.close_connection()
  135 
  136   def resume(self):
  137     """Resumes all paused snippet clients."""
  138     for client in self._snippet_clients.values():
  139       if not client.is_alive:
  140         self._device.log.debug('Resuming SnippetClient<%s>.', client.package)
  141         client.restore_server_connection()
  142       else:
  143         self._device.log.debug('Not resuming SnippetClient<%s>.',
  144                                client.package)
  145 
  146   def __getattr__(self, name):
  147     client = self.get_snippet_client(name)
  148     if client:
  149       return client
  150     return self.__getattribute__(name)
  151
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/mobly/controllers/android_device_lib/services/snippet_management_service.py b/mobly/controllers/android_device_lib/services/snippet_management_service.py
  --- a/mobly/controllers/android_device_lib/services/snippet_management_service.py
  +++ b/mobly/controllers/android_device_lib/services/snippet_management_service.py
  @@ -18,10 +18,6 @@
   
   MISSING_SNIPPET_CLIENT_MSG = 'No snippet client is registered with name "%s".'
   
  -# This config is transient and we will remove it after completing the migration
  -# from v1 to v2.
  -_CLIENT_V2_CONFIG_KEY = 'use_mobly_snippet_client_v2'
  -
   
   class Error(errors.ServiceError):
     """Root error type for snippet management service."""
  
verification_info: {"golden_diff": "diff --git a/mobly/controllers/android_device_lib/services/snippet_management_service.py b/mobly/controllers/android_device_lib/services/snippet_management_service.py\n--- a/mobly/controllers/android_device_lib/services/snippet_management_service.py\n+++ b/mobly/controllers/android_device_lib/services/snippet_management_service.py\n@@ -18,10 +18,6 @@\n \n MISSING_SNIPPET_CLIENT_MSG = 'No snippet client is registered with name \"%s\".'\n \n-# This config is transient and we will remove it after completing the migration\n-# from v1 to v2.\n-_CLIENT_V2_CONFIG_KEY = 'use_mobly_snippet_client_v2'\n-\n \n class Error(errors.ServiceError):\n   \"\"\"Root error type for snippet management service.\"\"\"\n", "issue": "Refactor Snippet Client\nMobly supports testing devices of multiple platforms, including Android, iOS and Windows. Mobly has one snippet client for each platform, and these clients share common patterns. So we want to consolidate current clients that they inherit from the same base class.\r\n\r\nCurrently, the father class of Android snippet client has too much Android-specific code, so it's better to add a new general base class. We will add a base class and one client V2 for each platform and we won't modify existing clients.\r\n\r\nThis issue is also a prerequisite of making iOS related code open source.\r\n\n", "before_files": [{"content": "# Copyright 2018 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Module for the snippet management service.\"\"\"\nfrom mobly.controllers.android_device_lib import errors\nfrom mobly.controllers.android_device_lib import snippet_client_v2\nfrom mobly.controllers.android_device_lib.services import base_service\n\nMISSING_SNIPPET_CLIENT_MSG = 'No snippet client is registered with name \"%s\".'\n\n# This config is transient and we will remove it after completing the migration\n# from v1 to v2.\n_CLIENT_V2_CONFIG_KEY = 'use_mobly_snippet_client_v2'\n\n\nclass Error(errors.ServiceError):\n  \"\"\"Root error type for snippet management service.\"\"\"\n  SERVICE_TYPE = 'SnippetManagementService'\n\n\nclass SnippetManagementService(base_service.BaseService):\n  \"\"\"Management service of snippet clients.\n\n  This service manages all the snippet clients associated with an Android\n  device.\n  \"\"\"\n\n  def __init__(self, device, configs=None):\n    del configs  # Unused param.\n    self._device = device\n    self._is_alive = False\n    self._snippet_clients = {}\n    super().__init__(device)\n\n  @property\n  def is_alive(self):\n    \"\"\"True if any client is running, False otherwise.\"\"\"\n    return any([client.is_alive for client in self._snippet_clients.values()])\n\n  def get_snippet_client(self, name):\n    \"\"\"Gets the snippet client managed under a given name.\n\n    Args:\n      name: string, the name of the snippet client under management.\n\n    Returns:\n      SnippetClient.\n    \"\"\"\n    if name in self._snippet_clients:\n      return self._snippet_clients[name]\n\n  def add_snippet_client(self, name, package):\n    \"\"\"Adds a snippet client to the management.\n\n    Args:\n      name: string, the attribute name to which to attach the snippet\n        client. E.g. `name='maps'` attaches the snippet client to\n        `ad.maps`.\n      package: string, the package name of the snippet apk to connect to.\n\n    Raises:\n      Error, if a duplicated name or package is passed in.\n    \"\"\"\n    # Should not load snippet with the same name more than once.\n    if name in self._snippet_clients:\n      raise Error(\n          self, 'Name \"%s\" is already registered with package \"%s\", it cannot '\n          'be used again.' % (name, self._snippet_clients[name].client.package))\n    # Should not load the same snippet package more than once.\n    for snippet_name, client in self._snippet_clients.items():\n      if package == client.package:\n        raise Error(\n            self, 'Snippet package \"%s\" has already been loaded under name'\n            ' \"%s\".' % (package, snippet_name))\n\n    client = snippet_client_v2.SnippetClientV2(package=package, ad=self._device)\n    client.initialize()\n    self._snippet_clients[name] = client\n\n  def remove_snippet_client(self, name):\n    \"\"\"Removes a snippet client from management.\n\n    Args:\n      name: string, the name of the snippet client to remove.\n\n    Raises:\n      Error: if no snippet client is managed under the specified name.\n    \"\"\"\n    if name not in self._snippet_clients:\n      raise Error(self._device, MISSING_SNIPPET_CLIENT_MSG % name)\n    client = self._snippet_clients.pop(name)\n    client.stop()\n\n  def start(self):\n    \"\"\"Starts all the snippet clients under management.\"\"\"\n    for client in self._snippet_clients.values():\n      if not client.is_alive:\n        self._device.log.debug('Starting SnippetClient<%s>.', client.package)\n        client.initialize()\n      else:\n        self._device.log.debug(\n            'Not startng SnippetClient<%s> because it is already alive.',\n            client.package)\n\n  def stop(self):\n    \"\"\"Stops all the snippet clients under management.\"\"\"\n    for client in self._snippet_clients.values():\n      if client.is_alive:\n        self._device.log.debug('Stopping SnippetClient<%s>.', client.package)\n        client.stop()\n      else:\n        self._device.log.debug(\n            'Not stopping SnippetClient<%s> because it is not alive.',\n            client.package)\n\n  def pause(self):\n    \"\"\"Pauses all the snippet clients under management.\n\n    This clears the host port of a client because a new port will be\n    allocated in `resume`.\n    \"\"\"\n    for client in self._snippet_clients.values():\n      self._device.log.debug('Pausing SnippetClient<%s>.', client.package)\n      client.close_connection()\n\n  def resume(self):\n    \"\"\"Resumes all paused snippet clients.\"\"\"\n    for client in self._snippet_clients.values():\n      if not client.is_alive:\n        self._device.log.debug('Resuming SnippetClient<%s>.', client.package)\n        client.restore_server_connection()\n      else:\n        self._device.log.debug('Not resuming SnippetClient<%s>.',\n                               client.package)\n\n  def __getattr__(self, name):\n    client = self.get_snippet_client(name)\n    if client:\n      return client\n    return self.__getattribute__(name)\n", "path": "mobly/controllers/android_device_lib/services/snippet_management_service.py"}], "after_files": [{"content": "# Copyright 2018 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Module for the snippet management service.\"\"\"\nfrom mobly.controllers.android_device_lib import errors\nfrom mobly.controllers.android_device_lib import snippet_client_v2\nfrom mobly.controllers.android_device_lib.services import base_service\n\nMISSING_SNIPPET_CLIENT_MSG = 'No snippet client is registered with name \"%s\".'\n\n\nclass Error(errors.ServiceError):\n  \"\"\"Root error type for snippet management service.\"\"\"\n  SERVICE_TYPE = 'SnippetManagementService'\n\n\nclass SnippetManagementService(base_service.BaseService):\n  \"\"\"Management service of snippet clients.\n\n  This service manages all the snippet clients associated with an Android\n  device.\n  \"\"\"\n\n  def __init__(self, device, configs=None):\n    del configs  # Unused param.\n    self._device = device\n    self._is_alive = False\n    self._snippet_clients = {}\n    super().__init__(device)\n\n  @property\n  def is_alive(self):\n    \"\"\"True if any client is running, False otherwise.\"\"\"\n    return any([client.is_alive for client in self._snippet_clients.values()])\n\n  def get_snippet_client(self, name):\n    \"\"\"Gets the snippet client managed under a given name.\n\n    Args:\n      name: string, the name of the snippet client under management.\n\n    Returns:\n      SnippetClient.\n    \"\"\"\n    if name in self._snippet_clients:\n      return self._snippet_clients[name]\n\n  def add_snippet_client(self, name, package):\n    \"\"\"Adds a snippet client to the management.\n\n    Args:\n      name: string, the attribute name to which to attach the snippet\n        client. E.g. `name='maps'` attaches the snippet client to\n        `ad.maps`.\n      package: string, the package name of the snippet apk to connect to.\n\n    Raises:\n      Error, if a duplicated name or package is passed in.\n    \"\"\"\n    # Should not load snippet with the same name more than once.\n    if name in self._snippet_clients:\n      raise Error(\n          self, 'Name \"%s\" is already registered with package \"%s\", it cannot '\n          'be used again.' % (name, self._snippet_clients[name].client.package))\n    # Should not load the same snippet package more than once.\n    for snippet_name, client in self._snippet_clients.items():\n      if package == client.package:\n        raise Error(\n            self, 'Snippet package \"%s\" has already been loaded under name'\n            ' \"%s\".' % (package, snippet_name))\n\n    client = snippet_client_v2.SnippetClientV2(package=package, ad=self._device)\n    client.initialize()\n    self._snippet_clients[name] = client\n\n  def remove_snippet_client(self, name):\n    \"\"\"Removes a snippet client from management.\n\n    Args:\n      name: string, the name of the snippet client to remove.\n\n    Raises:\n      Error: if no snippet client is managed under the specified name.\n    \"\"\"\n    if name not in self._snippet_clients:\n      raise Error(self._device, MISSING_SNIPPET_CLIENT_MSG % name)\n    client = self._snippet_clients.pop(name)\n    client.stop()\n\n  def start(self):\n    \"\"\"Starts all the snippet clients under management.\"\"\"\n    for client in self._snippet_clients.values():\n      if not client.is_alive:\n        self._device.log.debug('Starting SnippetClient<%s>.', client.package)\n        client.initialize()\n      else:\n        self._device.log.debug(\n            'Not startng SnippetClient<%s> because it is already alive.',\n            client.package)\n\n  def stop(self):\n    \"\"\"Stops all the snippet clients under management.\"\"\"\n    for client in self._snippet_clients.values():\n      if client.is_alive:\n        self._device.log.debug('Stopping SnippetClient<%s>.', client.package)\n        client.stop()\n      else:\n        self._device.log.debug(\n            'Not stopping SnippetClient<%s> because it is not alive.',\n            client.package)\n\n  def pause(self):\n    \"\"\"Pauses all the snippet clients under management.\n\n    This clears the host port of a client because a new port will be\n    allocated in `resume`.\n    \"\"\"\n    for client in self._snippet_clients.values():\n      self._device.log.debug('Pausing SnippetClient<%s>.', client.package)\n      client.close_connection()\n\n  def resume(self):\n    \"\"\"Resumes all paused snippet clients.\"\"\"\n    for client in self._snippet_clients.values():\n      if not client.is_alive:\n        self._device.log.debug('Resuming SnippetClient<%s>.', client.package)\n        client.restore_server_connection()\n      else:\n        self._device.log.debug('Not resuming SnippetClient<%s>.',\n                               client.package)\n\n  def __getattr__(self, name):\n    client = self.get_snippet_client(name)\n    if client:\n      return client\n    return self.__getattribute__(name)\n", "path": "mobly/controllers/android_device_lib/services/snippet_management_service.py"}]}