FAFT: Implement basic methods of ModeSwitcher for Ryu

This ModeSwitcher does some basic methods, like waiting DUT online/offline.
More changes will be added to support the rest.

BUG=chromium:527484
TEST=Ran some FAFT tests on Ryu.

Change-Id: I085ae9ed9c8fec5b773e3f9c6d6506bc008de5ba
Reviewed-on: https://chromium-review.googlesource.com/294774
Commit-Ready: Wai-Hong Tam <waihong@chromium.org>
Tested-by: Wai-Hong Tam <waihong@chromium.org>
Reviewed-by: Shelley Chen <shchen@chromium.org>
diff --git a/client/cros/faft/rpc_functions.py b/client/cros/faft/rpc_functions.py
index 64332b1..caeaca8 100755
--- a/client/cros/faft/rpc_functions.py
+++ b/client/cros/faft/rpc_functions.py
@@ -179,6 +179,23 @@
         """
         return True
 
+    def _system_wait_for_client(self, timeout):
+        """Wait for the client to come back online.
+
+        @param timeout: Time in seconds to wait for the client SSH daemon to
+                        come up.
+        @return: True if succeed; otherwise False.
+        """
+        return self._os_if.wait_for_device(timeout)
+
+    def _system_wait_for_client_offline(self, timeout):
+        """Wait for the client to come offline.
+
+        @param timeout: Time in seconds to wait the client to come offline.
+        @return: True if succeed; otherwise False.
+        """
+        return self._os_if.wait_for_no_device(timeout)
+
     def _system_dump_log(self, remove_log=False):
         """Dump the log file.
 
diff --git a/client/cros/faft/utils/os_interface.py b/client/cros/faft/utils/os_interface.py
index f968b16..9bf066a 100644
--- a/client/cros/faft/utils/os_interface.py
+++ b/client/cros/faft/utils/os_interface.py
@@ -179,6 +179,14 @@
         """Get a full path of a file in the state directory."""
         return os.path.join(self.state_dir, file_name)
 
+    def wait_for_device(self, timeout):
+        """Wait for an Android device to be connected."""
+        return self.shell.wait_for_device(timeout)
+
+    def wait_for_no_device(self, timeout):
+        """Wait for no Android device to be connected (offline)."""
+        return self.shell.wait_for_no_device(timeout)
+
     def log(self, text):
         """Write text to the log file and print it on the screen, if enabled.
 
diff --git a/client/cros/faft/utils/shell_wrapper.py b/client/cros/faft/utils/shell_wrapper.py
index 75944f8..4f394b6 100644
--- a/client/cros/faft/utils/shell_wrapper.py
+++ b/client/cros/faft/utils/shell_wrapper.py
@@ -172,3 +172,14 @@
             self._host_shell.append_file(f.name, data)
             cmd = 'adb push %s %s' % (f.name, path)
             self._host_shell.run_command(cmd)
+
+    def wait_for_device(self, timeout):
+        """Wait for an Android device connected."""
+        cmd = 'timeout %s adb wait-for-device' % timeout
+        return self._host_shell.run_command_get_status(cmd) == 0
+
+    def wait_for_no_device(self, timeout):
+        """Wait for no Android connected (offline)."""
+        cmd = ('for i in $(seq 0 %d); do adb shell sleep 1 || false; done' %
+               timeout)
+        return self._host_shell.run_command_get_status(cmd) != 0
diff --git a/server/cros/faft/utils/mode_switcher.py b/server/cros/faft/utils/mode_switcher.py
index 125648a..612f7c7 100644
--- a/server/cros/faft/utils/mode_switcher.py
+++ b/server/cros/faft/utils/mode_switcher.py
@@ -164,9 +164,13 @@
     if bypasser_type == 'ctrl_d_bypasser':
         logging.info('Create a CtrlDBypasser')
         return _CtrlDBypasser(servo, faft_config)
-    if bypasser_type == 'jetstream_bypasser':
+    elif bypasser_type == 'jetstream_bypasser':
         logging.info('Create a JetstreamBypasser')
         return _JetstreamBypasser(servo, faft_config)
+    elif bypasser_type == 'ryu_bypasser':
+        # FIXME Create an RyuBypasser
+        logging.info('Create a CtrlDBypasser')
+        return _CtrlDBypasser(servo, faft_config)
     else:
         raise NotImplementedError('Not supported fw_bypasser_type: %s',
                                   bypasser_type)
@@ -477,6 +481,46 @@
         self._disable_rec_mode_and_reboot(usb_state='host')
 
 
+class _RyuSwitcher(_BaseModeSwitcher):
+    """Class that switches firmware mode via physical button."""
+
+    def wait_for_client(self, timeout=180):
+        """Wait for the client to come back online.
+
+        New remote processes will be launched if their used flags are enabled.
+
+        @param timeout: Time in seconds to wait for the client SSH daemon to
+                        come up.
+        @raise ConnectionError: Failed to connect DUT.
+        """
+        if not self.faft_client.system.wait_for_client(timeout):
+            raise ConnectionError()
+
+
+    def wait_for_client_offline(self, timeout=60, orig_boot_id=None):
+        """Wait for the client to come offline.
+
+        @param timeout: Time in seconds to wait the client to come offline.
+        @param orig_boot_id: A string containing the original boot id.
+        @raise ConnectionError: Failed to wait DUT offline.
+        """
+        # TODO: Add a way to check orig_boot_id
+        if not self.faft_client.system.wait_for_client_offline(timeout):
+            raise ConnectionError()
+
+
+    def _enable_dev_mode_and_reboot(self):
+        """Switch to developer mode and reboot."""
+        # FIXME Implement switching to dev mode.
+        pass
+
+
+    def _enable_normal_mode_and_reboot(self):
+        """Switch to normal mode and reboot."""
+        # FIXME Implement switching to normal mode.
+        pass
+
+
 def create_mode_switcher(faft_framework):
     """Creates a proper mode switcher.
 
@@ -492,6 +536,9 @@
     elif switcher_type == 'jetstream_switcher':
         logging.info('Create a JetstreamSwitcher')
         return _JetstreamSwitcher(faft_framework)
+    elif switcher_type == 'ryu_switcher':
+        logging.info('Create a RyuSwitcher')
+        return _RyuSwitcher(faft_framework)
     else:
         raise NotImplementedError('Not supported mode_switcher_type: %s',
                                   switcher_type)